|
|
@@ -225,6 +225,26 @@ public class AirTicketBasicInfo
|
|
|
/// </summary>
|
|
|
public string ArrivedTime { get; set; }
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// 出发航站楼
|
|
|
+ /// </summary>
|
|
|
+ public string DepartureTerminal { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 抵达航站楼
|
|
|
+ /// </summary>
|
|
|
+ public string ArrivalTerminal { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 机型(如:359、321、32A、73H)
|
|
|
+ /// </summary>
|
|
|
+ public string AircraftType { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 耗时(如:11H10M、00H55M、01H35M)
|
|
|
+ /// </summary>
|
|
|
+ public string Duration { get; set; }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -339,406 +359,428 @@ public class AdditionalService
|
|
|
/// </summary>
|
|
|
public class FlightInfo
|
|
|
{
|
|
|
- public int SequenceNo { get; set; } // 序号
|
|
|
- public string FlightNumber { get; set; } // 航班号
|
|
|
- public string CabinClass { get; set; } // 舱位等级
|
|
|
- public string WeekDay { get; set; } // 星期(MO,TU,WE,TH,FR,SA,SU)
|
|
|
- public string DepartureDateRaw { get; set; } // 原始日期字符串
|
|
|
- public DateTime DepartureDate { get; set; } // 起飞日期
|
|
|
- public string DepartureTimeRaw { get; set; } // 原始起飞时间
|
|
|
- public string DepartureTime { get; set; } // 格式化起飞时间
|
|
|
- public string ArrivalTimeRaw { get; set; } // 原始到达时间
|
|
|
- public string ArrivalTime { get; set; } // 格式化到达时间
|
|
|
- public string DepartureAirport { get; set; } // 起飞机场
|
|
|
- public string ArrivalAirport { get; set; } // 到达机场
|
|
|
- public bool IsNextDayArrival { get; set; } // 是否次日到达
|
|
|
- public int NextDayCount { get; set; } // 跨天天数(+1表示第二天)
|
|
|
- public string Terminal { get; set; } // 航站楼
|
|
|
- public string AircraftType { get; set; } // 机型
|
|
|
- public string Duration { get; set; } // 飞行时长
|
|
|
- public string Status { get; set; } // 状态(HK1,KK1,TK1等)
|
|
|
- public string RawText { get; set; } // 原始文本
|
|
|
+ /// <summary>
|
|
|
+ /// 航段序号
|
|
|
+ /// </summary>
|
|
|
+ public int SequenceNo { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 航班号
|
|
|
+ /// 例如:CA431
|
|
|
+ /// </summary>
|
|
|
+ public string FlightNumber { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 星期
|
|
|
+ /// 例如:TU
|
|
|
+ /// </summary>
|
|
|
+ public string WeekDay { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 起飞日期
|
|
|
+ /// </summary>
|
|
|
+ public DateTime DepartureDate { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 出发机场三字码
|
|
|
+ /// 例如:TFU
|
|
|
+ /// </summary>
|
|
|
+ public string DepartureAirport { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 到达机场三字码
|
|
|
+ /// 例如:FRA
|
|
|
+ /// </summary>
|
|
|
+ public string ArrivalAirport { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 起飞时间
|
|
|
+ /// 格式:HH:mm
|
|
|
+ /// </summary>
|
|
|
+ public string DepartureTime { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 到达时间
|
|
|
+ /// 格式:HH:mm
|
|
|
+ /// </summary>
|
|
|
+ public string ArrivalTime { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 是否跨天到达
|
|
|
+ /// </summary>
|
|
|
+ public bool IsNextDayArrival { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 跨天数量
|
|
|
+ /// 例如:
|
|
|
+ /// +1 表示次日到达
|
|
|
+ /// +2 表示第三天到达
|
|
|
+ /// </summary>
|
|
|
+ public int NextDayCount { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 出发航站楼
|
|
|
+ /// 例如:T1
|
|
|
+ /// </summary>
|
|
|
+ public string DepartureTerminal { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 到达航站楼
|
|
|
+ /// 例如:T2
|
|
|
+ /// </summary>
|
|
|
+ public string ArrivalTerminal { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 机型代码
|
|
|
+ /// 例如:
|
|
|
+ /// 359=A350-900
|
|
|
+ /// 321=A321
|
|
|
+ /// 32A=A320neo
|
|
|
+ /// </summary>
|
|
|
+ public string AircraftType { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 飞行时长
|
|
|
+ /// 例如:11H10M
|
|
|
+ /// </summary>
|
|
|
+ public string Duration { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 原始文本
|
|
|
+ /// </summary>
|
|
|
+ public string RawText { get; set; }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 航班解析器
|
|
|
+/// 支持格式:
|
|
|
+/// 1.CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M
|
|
|
+/// 2.LH098 TU28MAY FRAMUC 0915 1010 1 2 321 00H55M
|
|
|
+/// 3.LH2440 TH29MAY MUCCPH 1025 1200 2 2 32A 01H35M
|
|
|
+/// 4.CA878 SA31MAY CPHPEK 1905 1000+1 3 T3 359 08H55M
|
|
|
/// </summary>
|
|
|
public static class FlightParser
|
|
|
{
|
|
|
- #region 常量定义
|
|
|
-
|
|
|
/// <summary>
|
|
|
- /// 月份映射
|
|
|
+ /// 月份映射表
|
|
|
/// </summary>
|
|
|
private static readonly Dictionary<string, int> MonthMap = new()
|
|
|
{
|
|
|
- { "JAN", 1 }, { "FEB", 2 }, { "MAR", 3 }, { "APR", 4 },
|
|
|
- { "MAY", 5 }, { "JUN", 6 }, { "JUL", 7 }, { "AUG", 8 },
|
|
|
- { "SEP", 9 }, { "OCT", 10 }, { "NOV", 11 }, { "DEC", 12 }
|
|
|
+ ["JAN"] = 1,
|
|
|
+ ["FEB"] = 2,
|
|
|
+ ["MAR"] = 3,
|
|
|
+ ["APR"] = 4,
|
|
|
+ ["MAY"] = 5,
|
|
|
+ ["JUN"] = 6,
|
|
|
+ ["JUL"] = 7,
|
|
|
+ ["AUG"] = 8,
|
|
|
+ ["SEP"] = 9,
|
|
|
+ ["OCT"] = 10,
|
|
|
+ ["NOV"] = 11,
|
|
|
+ ["DEC"] = 12
|
|
|
};
|
|
|
|
|
|
/// <summary>
|
|
|
- /// 星期映射
|
|
|
+ /// 解析航班信息
|
|
|
+ /// 自动推断年份
|
|
|
/// </summary>
|
|
|
- private static readonly Dictionary<string, string> WeekDayMap = new()
|
|
|
+ public static List<FlightInfo> ParseFlights(string text)
|
|
|
{
|
|
|
- { "MO", "周一" }, { "TU", "周二" }, { "WE", "周三" }, { "TH", "周四" },
|
|
|
- { "FR", "周五" }, { "SA", "周六" }, { "SU", "周日" }
|
|
|
- };
|
|
|
+ var result = new List<FlightInfo>();
|
|
|
|
|
|
- #endregion
|
|
|
+ if (string.IsNullOrWhiteSpace(text))
|
|
|
+ return result;
|
|
|
|
|
|
- #region 主解析方法
|
|
|
+ // 支持:
|
|
|
+ // 1.CA431 ... 2.LH098 ...
|
|
|
+ // 或
|
|
|
+ // 1.CA431 ...
|
|
|
+ // 2.LH098 ...
|
|
|
+ var matches = Regex.Matches(
|
|
|
+ text,
|
|
|
+ @"\d+\..*?(?=\d+\.|$)",
|
|
|
+ RegexOptions.Singleline);
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 解析航班文本(自动识别格式)
|
|
|
- /// </summary>
|
|
|
- public static List<FlightInfo> ParseFlights(string rawText, int year = 2024)
|
|
|
- {
|
|
|
- if (string.IsNullOrWhiteSpace(rawText))
|
|
|
- return new List<FlightInfo>();
|
|
|
-
|
|
|
- var lines = rawText.Trim().Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
- var result = new List<FlightInfo>();
|
|
|
-
|
|
|
- foreach (var line in lines)
|
|
|
+ foreach (Match match in matches)
|
|
|
{
|
|
|
- var flight = ParseLine(line, year);
|
|
|
+ var flight = ParseLine(match.Value.Trim());
|
|
|
+
|
|
|
if (flight != null)
|
|
|
{
|
|
|
result.Add(flight);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return result.OrderBy(x => x.SequenceNo).ToList();
|
|
|
+ // 按序号排序
|
|
|
+ result = result
|
|
|
+ .OrderBy(x => x.SequenceNo)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ // 自动修正跨年
|
|
|
+ FixYear(result);
|
|
|
+
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// 解析单行文本(自动识别格式)
|
|
|
+ /// 解析单条航班
|
|
|
/// </summary>
|
|
|
- private static FlightInfo ParseLine(string line, int year)
|
|
|
+ private static FlightInfo ParseLine(string line)
|
|
|
{
|
|
|
- line = line.Trim();
|
|
|
- if (string.IsNullOrEmpty(line))
|
|
|
+ var seqMatch = Regex.Match(line, @"^(\d+)\.");
|
|
|
+
|
|
|
+ if (!seqMatch.Success)
|
|
|
return null;
|
|
|
|
|
|
- // 格式1:HU7086 C MO18MAY TFUHAK HK1 1605 1820 E T2T2
|
|
|
- if (line.Contains("HK1") || line.Contains("KK1") || line.Contains("TK1"))
|
|
|
+ var flight = new FlightInfo
|
|
|
{
|
|
|
- return ParseFormat1(line, year);
|
|
|
- }
|
|
|
+ SequenceNo = int.Parse(seqMatch.Groups[1].Value),
|
|
|
+ RawText = line
|
|
|
+ };
|
|
|
|
|
|
- // 格式2:1.CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M
|
|
|
- // 格式2变体:2. LH098 TU28MAY FRAMUC 0915 1010 1 2 321 00H55M
|
|
|
- if (Regex.IsMatch(line, @"^\d+\.[A-Z0-9]+\s+[A-Z]{2}\d{2}[A-Z]{3}"))
|
|
|
- {
|
|
|
- return ParseFormat2(line, year);
|
|
|
- }
|
|
|
+ string content = line.Substring(seqMatch.Length).Trim();
|
|
|
|
|
|
- return null;
|
|
|
- }
|
|
|
+ var parts = Regex.Split(content, @"\s+");
|
|
|
|
|
|
- #endregion
|
|
|
+ if (parts.Length < 5)
|
|
|
+ return null;
|
|
|
|
|
|
- #region 格式1解析
|
|
|
+ int idx = 0;
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 解析格式1
|
|
|
- /// </summary>
|
|
|
- private static FlightInfo ParseFormat1(string line, int year)
|
|
|
- {
|
|
|
- // 提取序号
|
|
|
- var seqMatch = Regex.Match(line, @"^(\d+)\.\s*");
|
|
|
- if (!seqMatch.Success) return null;
|
|
|
+ // 航班号
|
|
|
+ flight.FlightNumber = parts[idx++];
|
|
|
|
|
|
- var sequenceNo = int.Parse(seqMatch.Groups[1].Value);
|
|
|
- var content = line.Substring(seqMatch.Length).Trim();
|
|
|
+ // 日期
|
|
|
+ string dateText = parts[idx++];
|
|
|
|
|
|
- // 正则匹配
|
|
|
- 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]+)";
|
|
|
- var match = Regex.Match(content, pattern);
|
|
|
+ flight.WeekDay = dateText[..2];
|
|
|
|
|
|
- if (!match.Success) return null;
|
|
|
+ // 这里只解析月日
|
|
|
+ flight.DepartureDate = ParseMonthDay(dateText);
|
|
|
|
|
|
- var weekDay = match.Groups[3].Value;
|
|
|
- var dateRaw = match.Groups[4].Value;
|
|
|
- var departureAirport = match.Groups[5].Value;
|
|
|
- var arrivalAirport = match.Groups[6].Value;
|
|
|
- var departureTime = match.Groups[8].Value;
|
|
|
- var arrivalTime = match.Groups[9].Value;
|
|
|
- var isNextDay = match.Groups[10].Success && match.Groups[10].Value == "1";
|
|
|
- var terminalFlag = match.Groups[12].Value;
|
|
|
+ // 航线
|
|
|
+ string route = parts[idx++];
|
|
|
|
|
|
- return new FlightInfo
|
|
|
+ if (route.Length >= 6)
|
|
|
{
|
|
|
- SequenceNo = sequenceNo,
|
|
|
- FlightNumber = match.Groups[1].Value,
|
|
|
- CabinClass = match.Groups[2].Value,
|
|
|
- WeekDay = weekDay,
|
|
|
- DepartureDateRaw = $"{weekDay}{dateRaw}",
|
|
|
- DepartureAirport = departureAirport,
|
|
|
- ArrivalAirport = arrivalAirport,
|
|
|
- DepartureTimeRaw = departureTime,
|
|
|
- ArrivalTimeRaw = arrivalTime,
|
|
|
- IsNextDayArrival = isNextDay,
|
|
|
- DepartureTime = FormatTime(departureTime),
|
|
|
- ArrivalTime = FormatTime(arrivalTime),
|
|
|
- DepartureDate = ParseDate(dateRaw, year),
|
|
|
- Status = match.Groups[7].Value,
|
|
|
- Terminal = terminalFlag,
|
|
|
- RawText = line
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region 格式2解析(修复版)
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 解析格式2
|
|
|
- /// 示例:1.CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M
|
|
|
- /// </summary>
|
|
|
- private static FlightInfo ParseFormat2(string line, int year)
|
|
|
- {
|
|
|
- // 1. 提取序号(支持 "1." 和 "1. ")
|
|
|
- var seqMatch = Regex.Match(line, @"^(\d+)\.\s*");
|
|
|
- if (!seqMatch.Success) return null;
|
|
|
+ flight.DepartureAirport = route[..3];
|
|
|
+ flight.ArrivalAirport = route.Substring(3, 3);
|
|
|
+ }
|
|
|
|
|
|
- var sequenceNo = int.Parse(seqMatch.Groups[1].Value);
|
|
|
- var content = line.Substring(seqMatch.Length).Trim();
|
|
|
+ // 起飞时间
|
|
|
+ flight.DepartureTime = FormatTime(parts[idx++]);
|
|
|
|
|
|
- // 2. 分割空格
|
|
|
- var parts = content.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
- if (parts.Length < 8) return null;
|
|
|
+ // 到达时间
|
|
|
+ string arrivalRaw = parts[idx++];
|
|
|
|
|
|
- try
|
|
|
+ if (arrivalRaw.Contains('+'))
|
|
|
{
|
|
|
- var flight = new FlightInfo
|
|
|
- {
|
|
|
- SequenceNo = sequenceNo,
|
|
|
- RawText = line
|
|
|
- };
|
|
|
+ var arr = arrivalRaw.Split('+');
|
|
|
|
|
|
- int idx = 0;
|
|
|
+ flight.ArrivalTime = FormatTime(arr[0]);
|
|
|
|
|
|
- // 航班号
|
|
|
- flight.FlightNumber = parts[idx++];
|
|
|
+ flight.IsNextDayArrival = true;
|
|
|
|
|
|
- // 日期(如 TU27MAY)
|
|
|
- var dateRaw = parts[idx++];
|
|
|
- if (dateRaw.Length >= 7)
|
|
|
+ if (arr.Length > 1)
|
|
|
{
|
|
|
- flight.WeekDay = dateRaw.Substring(0, 2);
|
|
|
- flight.DepartureDateRaw = dateRaw;
|
|
|
+ flight.NextDayCount = int.Parse(arr[1]);
|
|
|
}
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ flight.ArrivalTime = FormatTime(arrivalRaw);
|
|
|
+ }
|
|
|
|
|
|
- // 机场(如 TFUFRA 或分开的格式)
|
|
|
- var airportStr = parts[idx++];
|
|
|
- if (airportStr.Length >= 6)
|
|
|
- {
|
|
|
- // 连写格式:TFUFRA -> TFU + FRA
|
|
|
- flight.DepartureAirport = airportStr.Substring(0, 3);
|
|
|
- flight.ArrivalAirport = airportStr.Substring(3, 3);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // 分开格式
|
|
|
- flight.DepartureAirport = airportStr;
|
|
|
- flight.ArrivalAirport = parts[idx++];
|
|
|
- }
|
|
|
+ // 剩余字段
|
|
|
+ var remain = parts.Skip(idx).ToList();
|
|
|
|
|
|
- // 起飞时间
|
|
|
- flight.DepartureTimeRaw = parts[idx++];
|
|
|
- flight.DepartureTime = FormatTime(flight.DepartureTimeRaw);
|
|
|
+ ParseRemainFields(remain, flight);
|
|
|
|
|
|
- // 到达时间(可能带 +1)
|
|
|
- var arrivalRaw = parts[idx++];
|
|
|
- if (arrivalRaw.Contains('+'))
|
|
|
- {
|
|
|
- var arrParts = arrivalRaw.Split('+');
|
|
|
- flight.ArrivalTimeRaw = arrParts[0];
|
|
|
- flight.IsNextDayArrival = true;
|
|
|
- if (arrParts.Length > 1 && int.TryParse(arrParts[1], out var days))
|
|
|
- {
|
|
|
- flight.NextDayCount = days;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- flight.ArrivalTimeRaw = arrivalRaw;
|
|
|
- }
|
|
|
- flight.ArrivalTime = FormatTime(flight.ArrivalTimeRaw);
|
|
|
+ return flight;
|
|
|
+ }
|
|
|
|
|
|
- // 航站楼(可选,以T开头)
|
|
|
- if (idx < parts.Length && parts[idx].StartsWith("T"))
|
|
|
- {
|
|
|
- flight.Terminal = parts[idx++];
|
|
|
- }
|
|
|
+ ///// <summary>
|
|
|
+ /////
|
|
|
+ ///// </summary>
|
|
|
+ ///// <param name="remain">
|
|
|
+ ///// 例如:
|
|
|
+ ///// T1 1 359 11H10M
|
|
|
+ ///// 1 2 321 00H55M
|
|
|
+ ///// 3 T3 359 08H55M
|
|
|
+ ///// </param>
|
|
|
+ /////
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 解析剩余字段
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="remain">
|
|
|
+ /// 例如:
|
|
|
+ /// T1 1 359 11H10M
|
|
|
+ /// 1 2 321 00H55M
|
|
|
+ /// 3 T3 359 08H55M
|
|
|
+ /// </param>
|
|
|
+ /// <param name="flight"></param>
|
|
|
+ private static void ParseRemainFields(
|
|
|
+ List<string> remain,
|
|
|
+ FlightInfo flight)
|
|
|
+ {
|
|
|
+ if (remain.Count < 2)
|
|
|
+ return;
|
|
|
|
|
|
- // 序号(可选)
|
|
|
- if (idx < parts.Length && int.TryParse(parts[idx], out _))
|
|
|
- {
|
|
|
- // 这个序号可能是经停次数或行程序号
|
|
|
- idx++;
|
|
|
- }
|
|
|
+ // 最后一个字段一般是飞行时长
|
|
|
+ if (Regex.IsMatch(remain[^1], @"^\d{2}H\d{2}M$"))
|
|
|
+ {
|
|
|
+ flight.Duration = remain[^1];
|
|
|
|
|
|
- // 机型(可选,数字或数字+字母)
|
|
|
- if (idx < parts.Length && Regex.IsMatch(parts[idx], @"^\d{2,3}[A-Z]?$"))
|
|
|
- {
|
|
|
- flight.AircraftType = parts[idx++];
|
|
|
- }
|
|
|
+ remain.RemoveAt(remain.Count - 1);
|
|
|
+ }
|
|
|
|
|
|
- // 飞行时长(可选,包含H)
|
|
|
- if (idx < parts.Length && parts[idx].Contains('H'))
|
|
|
- {
|
|
|
- flight.Duration = parts[idx++];
|
|
|
- }
|
|
|
+ // 倒数第二个字段一般是机型
|
|
|
+ if (remain.Count > 0 &&
|
|
|
+ Regex.IsMatch(remain[^1], @"^\d{2,3}[A-Z]?$"))
|
|
|
+ {
|
|
|
+ flight.AircraftType = remain[^1];
|
|
|
|
|
|
- // 解析日期
|
|
|
- flight.DepartureDate = ParseDate(dateRaw, year);
|
|
|
+ remain.RemoveAt(remain.Count - 1);
|
|
|
+ }
|
|
|
|
|
|
- return flight;
|
|
|
+ // 剩余字段解析为航站楼
|
|
|
+ if (remain.Count == 1)
|
|
|
+ {
|
|
|
+ flight.DepartureTerminal =
|
|
|
+ NormalizeTerminal(remain[0]);
|
|
|
}
|
|
|
- catch
|
|
|
+ else if (remain.Count >= 2)
|
|
|
{
|
|
|
- return null;
|
|
|
+ flight.DepartureTerminal =
|
|
|
+ NormalizeTerminal(remain[0]);
|
|
|
+
|
|
|
+ flight.ArrivalTerminal =
|
|
|
+ NormalizeTerminal(remain[1]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- #endregion
|
|
|
+ /// <summary>
|
|
|
+ /// 标准化航站楼
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="terminal">
|
|
|
+ /// 原始值:1、2、 T3
|
|
|
+ /// </param>
|
|
|
+ /// <returns>
|
|
|
+ /// T1、T2、T3
|
|
|
+ /// </returns>
|
|
|
+ private static string NormalizeTerminal(string terminal)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrWhiteSpace(terminal))
|
|
|
+ return null;
|
|
|
+
|
|
|
+ terminal = terminal.Trim().ToUpper();
|
|
|
|
|
|
- #region 辅助方法
|
|
|
+ // 纯数字补T
|
|
|
+ if (Regex.IsMatch(terminal, @"^\d+$"))
|
|
|
+ {
|
|
|
+ return $"T{terminal}";
|
|
|
+ }
|
|
|
+
|
|
|
+ return terminal;
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
- /// 格式化时间(1905 -> 19:05)
|
|
|
+ /// 格式化时间
|
|
|
+ /// 0135 -> 01:35
|
|
|
+ /// 905 -> 09:05
|
|
|
/// </summary>
|
|
|
private static string FormatTime(string time)
|
|
|
{
|
|
|
if (string.IsNullOrWhiteSpace(time))
|
|
|
return string.Empty;
|
|
|
|
|
|
- // 移除 +1 等后缀
|
|
|
- var cleanTime = time.Split('+')[0];
|
|
|
+ time = time.Split('+')[0];
|
|
|
|
|
|
- // 4位数字
|
|
|
- if (cleanTime.Length == 4 && int.TryParse(cleanTime, out _))
|
|
|
+ if (time.Length == 4)
|
|
|
{
|
|
|
- return $"{cleanTime.Substring(0, 2)}:{cleanTime.Substring(2, 2)}";
|
|
|
+ return $"{time[..2]}:{time.Substring(2, 2)}";
|
|
|
}
|
|
|
|
|
|
- // 3位数字(905 -> 09:05)
|
|
|
- if (cleanTime.Length == 3 && int.TryParse(cleanTime, out _))
|
|
|
+ if (time.Length == 3)
|
|
|
{
|
|
|
- return $"0{cleanTime.Substring(0, 1)}:{cleanTime.Substring(1, 2)}";
|
|
|
+ return $"0{time[0]}:{time.Substring(1, 2)}";
|
|
|
}
|
|
|
|
|
|
- // 已经是 HH:mm 格式
|
|
|
- if (Regex.IsMatch(cleanTime, @"^\d{1,2}:\d{2}$"))
|
|
|
- {
|
|
|
- var parts = cleanTime.Split(':');
|
|
|
- return $"{parts[0].PadLeft(2, '0')}:{parts[1]}";
|
|
|
- }
|
|
|
-
|
|
|
- return cleanTime;
|
|
|
+ return time;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// 解析日期(27MAY -> 2024-05-27)
|
|
|
+ /// 自动修正跨年
|
|
|
/// </summary>
|
|
|
- private static DateTime ParseDate(string dateStr, int year)
|
|
|
+ private static void FixYear(
|
|
|
+ List<FlightInfo> flights)
|
|
|
{
|
|
|
- if (string.IsNullOrWhiteSpace(dateStr))
|
|
|
- return DateTime.MinValue;
|
|
|
+ if (flights.Count <= 1)
|
|
|
+ return;
|
|
|
|
|
|
- // 移除星期前缀
|
|
|
- var cleanDate = Regex.Replace(dateStr, @"^[A-Z]{2}", "");
|
|
|
+ int currentYear = DateTime.Now.Year;
|
|
|
|
|
|
- var match = Regex.Match(cleanDate, @"(\d{2})([A-Z]{3})");
|
|
|
- if (match.Success)
|
|
|
+ DateTime previousDate = DateTime.MinValue;
|
|
|
+
|
|
|
+ foreach (var flight in flights)
|
|
|
{
|
|
|
- var day = int.Parse(match.Groups[1].Value);
|
|
|
- var monthStr = match.Groups[2].Value;
|
|
|
+ if (flight.DepartureDate == DateTime.MinValue)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ var date = new DateTime(
|
|
|
+ currentYear,
|
|
|
+ flight.DepartureDate.Month,
|
|
|
+ flight.DepartureDate.Day);
|
|
|
|
|
|
- if (MonthMap.TryGetValue(monthStr.ToUpper(), out var month))
|
|
|
+ // 出现日期倒退
|
|
|
+ if (previousDate != DateTime.MinValue &&
|
|
|
+ date < previousDate)
|
|
|
{
|
|
|
- try
|
|
|
- {
|
|
|
- return new DateTime(year, month, day);
|
|
|
- }
|
|
|
- catch
|
|
|
- {
|
|
|
- return DateTime.MinValue;
|
|
|
- }
|
|
|
+ currentYear++;
|
|
|
+ date = new DateTime(
|
|
|
+ currentYear,
|
|
|
+ flight.DepartureDate.Month,
|
|
|
+ flight.DepartureDate.Day);
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- return DateTime.MinValue;
|
|
|
- }
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 获取完整起飞时间(含时分)
|
|
|
- /// </summary>
|
|
|
- public static DateTime GetFullDepartureTime(FlightInfo flight, int year)
|
|
|
- {
|
|
|
- if (flight.DepartureDate == DateTime.MinValue || string.IsNullOrEmpty(flight.DepartureTimeRaw))
|
|
|
- return DateTime.MinValue;
|
|
|
+ flight.DepartureDate = date;
|
|
|
|
|
|
- var time = ParseTimeToTimeSpan(flight.DepartureTimeRaw);
|
|
|
- return flight.DepartureDate.Add(time);
|
|
|
+ previousDate = date;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// 获取完整到达时间(含时分和跨天)
|
|
|
+ /// 解析月日
|
|
|
+ /// TU27MAY -> 05-27
|
|
|
/// </summary>
|
|
|
- public static DateTime GetFullArrivalTime(FlightInfo flight, int year)
|
|
|
+ private static DateTime ParseMonthDay(string value)
|
|
|
{
|
|
|
- if (flight.DepartureDate == DateTime.MinValue || string.IsNullOrEmpty(flight.ArrivalTimeRaw))
|
|
|
- return DateTime.MinValue;
|
|
|
-
|
|
|
- var time = ParseTimeToTimeSpan(flight.ArrivalTimeRaw);
|
|
|
- var arrivalDate = flight.DepartureDate.Add(time);
|
|
|
-
|
|
|
- if (flight.IsNextDayArrival)
|
|
|
- {
|
|
|
- arrivalDate = arrivalDate.AddDays(flight.NextDayCount > 0 ? flight.NextDayCount : 1);
|
|
|
- }
|
|
|
+ value = Regex.Replace(
|
|
|
+ value,
|
|
|
+ @"^[A-Z]{2}",
|
|
|
+ "");
|
|
|
|
|
|
- return arrivalDate;
|
|
|
- }
|
|
|
+ var match = Regex.Match(
|
|
|
+ value,
|
|
|
+ @"(\d{2})([A-Z]{3})");
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 解析时间字符串为 TimeSpan
|
|
|
- /// </summary>
|
|
|
- private static TimeSpan ParseTimeToTimeSpan(string time)
|
|
|
- {
|
|
|
- var cleanTime = time.Split('+')[0];
|
|
|
+ if (!match.Success)
|
|
|
+ return DateTime.MinValue;
|
|
|
|
|
|
- if (cleanTime.Length == 4)
|
|
|
- {
|
|
|
- return new TimeSpan(
|
|
|
- int.Parse(cleanTime.Substring(0, 2)),
|
|
|
- int.Parse(cleanTime.Substring(2, 2)),
|
|
|
- 0);
|
|
|
- }
|
|
|
+ int day = int.Parse(match.Groups[1].Value);
|
|
|
|
|
|
- if (cleanTime.Length == 3)
|
|
|
+ if (!MonthMap.TryGetValue(
|
|
|
+ match.Groups[2].Value,
|
|
|
+ out int month))
|
|
|
{
|
|
|
- return new TimeSpan(
|
|
|
- int.Parse(cleanTime.Substring(0, 1)),
|
|
|
- int.Parse(cleanTime.Substring(1, 2)),
|
|
|
- 0);
|
|
|
+ return DateTime.MinValue;
|
|
|
}
|
|
|
|
|
|
- return TimeSpan.Zero;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 获取星期中文名称
|
|
|
- /// </summary>
|
|
|
- public static string GetWeekDayName(string weekDay)
|
|
|
- {
|
|
|
- return WeekDayMap.TryGetValue(weekDay.ToUpper(), out var name) ? name : weekDay;
|
|
|
+ // 先使用当前年份
|
|
|
+ return new DateTime(
|
|
|
+ DateTime.Now.Year,
|
|
|
+ month,
|
|
|
+ day);
|
|
|
}
|
|
|
-
|
|
|
- #endregion
|
|
|
}
|