cardigann: invariant date string parsing (#14074)

This commit is contained in:
Bogdan 2023-02-25 23:00:15 +02:00 committed by GitHub
parent 37ff7ed991
commit f7e6884720
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 115 additions and 72 deletions

View File

@ -10,5 +10,18 @@ namespace Jackett.Common.Extensions
public static bool ContainsIgnoreCase(this string source, string contains) => source != null && contains != null && CultureInfo.InvariantCulture.CompareInfo.IndexOf(source, contains, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0;
public static bool ContainsIgnoreCase(this IEnumerable<string> source, string value) => source.Contains(value, StringComparer.InvariantCultureIgnoreCase);
public static bool IsAllDigits(this string input)
{
foreach (var c in input)
{
if (c < '0' || c > '9')
{
return false;
}
}
return true;
}
}
}

View File

@ -1013,19 +1013,14 @@ namespace Jackett.Common.Indexers
case "dateparse":
var layout = (string)Filter.Args;
if (layout.Contains("yy") && DateTime.TryParseExact(Data, layout, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsedDate))
Data = parsedDate.ToString(DateTimeUtil.Rfc1123ZPattern);
else
try
{
try
{
var datetime = DateTimeUtil.ParseDateTimeGoLang(Data, layout);
Data = datetime.ToString(DateTimeUtil.Rfc1123ZPattern);
}
catch (FormatException ex)
{
logger.Debug(ex.Message);
}
var datetime = DateTimeUtil.ParseDateTimeGoLang(Data, layout);
Data = datetime.ToString(DateTimeUtil.Rfc1123ZPattern, CultureInfo.InvariantCulture);
}
catch (FormatException ex)
{
logger.Debug(ex.Message);
}
break;
case "regexp":
@ -1093,10 +1088,10 @@ namespace Jackett.Common.Indexers
break;
case "timeago":
case "reltime":
Data = DateTimeUtil.FromTimeAgo(Data).ToString(DateTimeUtil.Rfc1123ZPattern);
Data = DateTimeUtil.FromTimeAgo(Data).ToString(DateTimeUtil.Rfc1123ZPattern, CultureInfo.InvariantCulture);
break;
case "fuzzytime":
Data = DateTimeUtil.FromUnknown(Data).ToString(DateTimeUtil.Rfc1123ZPattern);
Data = DateTimeUtil.FromUnknown(Data).ToString(DateTimeUtil.Rfc1123ZPattern, CultureInfo.InvariantCulture);
break;
case "validfilename":
Data = StringUtil.MakeValidFileName(Data, '_', false);
@ -1509,7 +1504,7 @@ namespace Jackett.Common.Indexers
variables[variablesKey] = null;
continue;
}
throw new Exception(string.Format("Error while parsing field={0}, selector={1}, value={2}: {3}", Field.Key, Field.Value.Selector, (value == null ? "<null>" : value), ex.Message));
throw new Exception($"Error while parsing field={Field.Key}, selector={Field.Value.Selector}, value={value ?? "<null>"}: {ex.Message}", ex);
}
}
@ -1646,7 +1641,7 @@ namespace Jackett.Common.Indexers
variables[variablesKey] = null;
continue;
}
throw new Exception(string.Format("Error while parsing field={0}, selector={1}, value={2}: {3}", Field.Key, Field.Value.Selector, (value == null ? "<null>" : value), ex.Message));
throw new Exception($"Error while parsing field={Field.Key}, selector={Field.Value.Selector}, value={value ?? "<null>"}: {ex.Message}", ex);
}
}
@ -2040,8 +2035,8 @@ namespace Jackett.Common.Indexers
value = release.Seeders.ToString();
break;
case "date":
release.PublishDate = DateTimeUtil.FromUnknown(value);
value = release.PublishDate.ToString(DateTimeUtil.Rfc1123ZPattern);
release.PublishDate = DateTime.TryParseExact(value, DateTimeUtil.Rfc1123ZPattern, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsedDate) ? parsedDate : DateTimeUtil.FromUnknown(value);
value = release.PublishDate.ToString(DateTimeUtil.Rfc1123ZPattern, CultureInfo.InvariantCulture);
break;
case "files":
release.Files = ParseUtil.CoerceLong(value);

View File

@ -2,6 +2,7 @@ using System;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Jackett.Common.Extensions;
namespace Jackett.Common.Utils
{
@ -117,7 +118,7 @@ namespace Jackett.Common.Utils
var now = relativeFrom ?? DateTime.Now;
// try parsing the str as an unix timestamp
if (str.All(char.IsDigit) && long.TryParse(str, out var unixTimeStamp))
if (str.IsAllDigits() && long.TryParse(str, out var unixTimeStamp))
return UnixTimestampToDateTime(unixTimeStamp);
if (str.ToLower().Contains("now"))
@ -227,81 +228,87 @@ namespace Jackett.Common.Utils
var now = relativeFrom ?? DateTime.Now;
date = ParseUtil.NormalizeSpace(date);
var pattern = layout;
// year
pattern = pattern.Replace("2006", "yyyy");
pattern = pattern.Replace("06", "yy");
var commonStandardFormats = new[] { "y", "h", "d" };
// month
pattern = pattern.Replace("January", "MMMM");
pattern = pattern.Replace("Jan", "MMM");
pattern = pattern.Replace("01", "MM");
if (commonStandardFormats.Any(layout.ContainsIgnoreCase) && DateTime.TryParseExact(date, layout, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsedDate))
return parsedDate;
// day
pattern = pattern.Replace("Monday", "dddd");
pattern = pattern.Replace("Mon", "ddd");
pattern = pattern.Replace("02", "dd");
//pattern = pattern.Replace("_2", ""); // space padding not supported nativly by C#?
pattern = pattern.Replace("2", "d");
var format = layout
// hours/minutes/seconds
pattern = pattern.Replace("05", "ss");
// year
.Replace("2006", "yyyy")
.Replace("06", "yy")
pattern = pattern.Replace("15", "HH");
pattern = pattern.Replace("03", "hh");
pattern = pattern.Replace("3", "h");
// month
.Replace("January", "MMMM")
.Replace("Jan", "MMM")
.Replace("01", "MM")
pattern = pattern.Replace("04", "mm");
pattern = pattern.Replace("4", "m");
// day
.Replace("Monday", "dddd")
.Replace("Mon", "ddd")
.Replace("02", "dd")
//pattern = pattern.Replace("_2", "") // space padding not supported nativly by C#?
.Replace("2", "d")
pattern = pattern.Replace("5", "s");
// hours/minutes/seconds
.Replace("05", "ss")
// month again
pattern = pattern.Replace("1", "M");
.Replace("15", "HH")
.Replace("03", "hh")
.Replace("3", "h")
// fractional seconds
pattern = pattern.Replace(".0000", "ffff");
pattern = pattern.Replace(".000", "fff");
pattern = pattern.Replace(".00", "ff");
pattern = pattern.Replace(".0", "f");
.Replace("04", "mm")
.Replace("4", "m")
pattern = pattern.Replace(".9999", "FFFF");
pattern = pattern.Replace(".999", "FFF");
pattern = pattern.Replace(".99", "FF");
pattern = pattern.Replace(".9", "F");
.Replace("5", "s")
// AM/PM
pattern = pattern.Replace("PM", "tt");
pattern = pattern.Replace("pm", "tt"); // not sure if this works
// month again
.Replace("1", "M")
// timezones
// these might need further tuning
//pattern = pattern.Replace("MST", "");
//pattern = pattern.Replace("Z07:00:00", "");
pattern = pattern.Replace("Z07:00", "'Z'zzz");
pattern = pattern.Replace("Z07", "'Z'zz");
//pattern = pattern.Replace("Z070000", "");
//pattern = pattern.Replace("Z0700", "");
pattern = pattern.Replace("Z07:00", "'Z'zzz");
pattern = pattern.Replace("Z07", "'Z'zz");
//pattern = pattern.Replace("-07:00:00", "");
pattern = pattern.Replace("-07:00", "zzz");
//pattern = pattern.Replace("-0700", "zz");
pattern = pattern.Replace("-07", "zz");
// fractional seconds
.Replace(".0000", "ffff")
.Replace(".000", "fff")
.Replace(".00", "ff")
.Replace(".0", "f")
.Replace(".9999", "FFFF")
.Replace(".999", "FFF")
.Replace(".99", "FF")
.Replace(".9", "F")
// AM/PM
.Replace("PM", "tt")
.Replace("pm", "tt") // not sure if this works
// timezones
// these might need further tuning
//pattern = pattern.Replace("MST", "")
//pattern = pattern.Replace("Z07:00:00", "")
.Replace("Z07:00", "'Z'zzz")
.Replace("Z07", "'Z'zz")
//pattern = pattern.Replace("Z070000", "")
//pattern = pattern.Replace("Z0700", "")
.Replace("Z07:00", "'Z'zzz")
.Replace("Z07", "'Z'zz")
//pattern = pattern.Replace("-07:00:00", "")
.Replace("-07:00", "zzz")
//pattern = pattern.Replace("-0700", "zz")
.Replace("-07", "zz");
try
{
var dateTime = DateTime.ParseExact(date, pattern, CultureInfo.InvariantCulture);
var dateTime = DateTime.ParseExact(date, format, CultureInfo.InvariantCulture);
if (!pattern.Contains("yy") && dateTime > now)
if (!format.Contains("yy") && dateTime > now)
dateTime = dateTime.AddYears(-1);
return dateTime;
}
catch (FormatException ex)
{
throw new FormatException($"Error while parsing DateTime \"{date}\", using layout \"{layout}\" ({pattern}): {ex.Message}", ex);
throw new FormatException($"Error while parsing DateTime \"{date}\", using layout \"{layout}\" ({format}): {ex.Message}", ex);
}
}

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using Jackett.Common.Utils;
using NUnit.Framework;
@ -178,5 +180,31 @@ namespace Jackett.Test.Common.Utils
var diff = Math.Abs((dt1 - dt2).TotalSeconds);
Assert.True(diff < delta, $"Dates are not similar. Expected: {dt1} But was: {dt2}");
}
[TestCase("pt-BR")]
[TestCase("en-US")]
public void AssertFormattingDatesInvariant(string culture)
{
Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
var dateNow = DateTime.Now;
Assert.AreEqual(
dateNow.ToString("ddd, dd MMM yyyy HH':'mm':'ss z", CultureInfo.InvariantCulture),
DateTimeUtil.FromUnknown(dateNow.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)).ToString(DateTimeUtil.Rfc1123ZPattern, CultureInfo.InvariantCulture));
}
[TestCase("2022-08-08 02:07:39 -02:00", "2006-01-02 15:04:05 -07:00", "yyyy-MM-dd HH:mm:ss zzz", "2022-08-08 04:07:39 +00:00")]
[TestCase("2022-08-08 02:07:39 -02:00", "yyyy-MM-dd HH:mm:ss zzz", "yyyy-MM-dd HH:mm:ss zzz", "2022-08-08 04:07:39 +00:00")]
[TestCase("2022-08-08 -02:00", "2006-01-02 -07:00", "yyyy-MM-dd zzz", "2022-08-08 +00:00")]
[TestCase("2022-08-08 -02:00", "yyyy-MM-dd zzz", "yyyy-MM-dd zzz", "2022-08-08 +00:00")]
[TestCase("02:07:39 -02:00", "15:04:05 -07:00", "HH:mm:ss zzz", "04:07:39 +00:00")]
[TestCase("02:07:39 -02:00", "HH:mm:ss zzz", "HH:mm:ss zzz", "04:07:39 +00:00")]
[TestCase("-02:00", "zzz", "zzz", "+00:00")]
[TestCase("-02:00", "-07:00", "zzz", "+00:00")]
public void AssertParsingDateTimeGolang(string dateInput, string format, string standardFormat, string expectedDate)
{
Assert.AreEqual(expectedDate, DateTimeUtil.ParseDateTimeGoLang(dateInput, format).ToUniversalTime().ToString(standardFormat, CultureInfo.InvariantCulture));
}
}
}