2020-02-09 02:35:16 +00:00
|
|
|
using System;
|
2017-04-15 08:45:10 +00:00
|
|
|
using System.Globalization;
|
|
|
|
using System.Text.RegularExpressions;
|
2015-07-19 17:18:54 +00:00
|
|
|
|
2018-03-10 08:05:56 +00:00
|
|
|
namespace Jackett.Common.Utils
|
2015-07-19 17:18:54 +00:00
|
|
|
{
|
2020-11-04 20:56:54 +00:00
|
|
|
/// <summary>
|
|
|
|
/// All functions MUST return local time (local time zone)
|
|
|
|
/// </summary>
|
2015-07-19 17:18:54 +00:00
|
|
|
public static class DateTimeUtil
|
|
|
|
{
|
2020-03-14 16:05:10 +00:00
|
|
|
public const string Rfc1123ZPattern = "ddd, dd MMM yyyy HH':'mm':'ss z";
|
|
|
|
|
|
|
|
private static readonly Regex _TimeAgoRegexp = new Regex(@"(?i)\bago", RegexOptions.Compiled);
|
2020-03-18 08:11:48 +00:00
|
|
|
private static readonly Regex _TodayRegexp = new Regex(@"(?i)\btoday(?:[\s,]+(?:at){0,1}\s*|[\s,]*|$)", RegexOptions.Compiled);
|
|
|
|
private static readonly Regex _TomorrowRegexp = new Regex(@"(?i)\btomorrow(?:[\s,]+(?:at){0,1}\s*|[\s,]*|$)", RegexOptions.Compiled);
|
|
|
|
private static readonly Regex _YesterdayRegexp = new Regex(@"(?i)\byesterday(?:[\s,]+(?:at){0,1}\s*|[\s,]*|$)", RegexOptions.Compiled);
|
2020-03-14 16:05:10 +00:00
|
|
|
private static readonly Regex _DaysOfWeekRegexp = new Regex(@"(?i)\b(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\s+at\s+", RegexOptions.Compiled);
|
|
|
|
private static readonly Regex _MissingYearRegexp = new Regex(@"^(\d{1,2}-\d{1,2})(\s|$)", RegexOptions.Compiled);
|
|
|
|
private static readonly Regex _MissingYearRegexp2 = new Regex(@"^(\d{1,2}\s+\w{3})\s+(\d{1,2}\:\d{1,2}.*)$", RegexOptions.Compiled); // 1 Jan 10:30
|
2016-10-27 07:30:03 +00:00
|
|
|
|
2016-12-17 15:42:50 +00:00
|
|
|
public static DateTime UnixTimestampToDateTime(long unixTime)
|
|
|
|
{
|
2020-03-14 16:05:10 +00:00
|
|
|
var dt = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
2016-12-17 15:42:50 +00:00
|
|
|
dt = dt.AddSeconds(unixTime).ToLocalTime();
|
|
|
|
return dt;
|
|
|
|
}
|
|
|
|
|
2015-07-19 17:18:54 +00:00
|
|
|
public static DateTime UnixTimestampToDateTime(double unixTime)
|
|
|
|
{
|
2020-03-14 16:05:10 +00:00
|
|
|
var unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
2020-02-10 22:16:19 +00:00
|
|
|
var unixTimeStampInTicks = (long)(unixTime * TimeSpan.TicksPerSecond);
|
2020-11-04 20:56:54 +00:00
|
|
|
return new DateTime(unixStart.Ticks + unixTimeStampInTicks).ToLocalTime();
|
2015-07-19 17:18:54 +00:00
|
|
|
}
|
2015-07-23 05:16:13 +00:00
|
|
|
|
2015-08-03 21:38:45 +00:00
|
|
|
public static double DateTimeToUnixTimestamp(DateTime dt)
|
|
|
|
{
|
|
|
|
var date = dt.ToUniversalTime();
|
|
|
|
var ticks = date.Ticks - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).Ticks;
|
|
|
|
var ts = ticks / TimeSpan.TicksPerSecond;
|
|
|
|
return ts;
|
|
|
|
}
|
|
|
|
|
2015-07-23 05:16:13 +00:00
|
|
|
// ex: "2 hours 1 day"
|
2021-05-03 19:26:43 +00:00
|
|
|
public static DateTime FromTimeAgo(string str, DateTime? relativeFrom = null)
|
2015-07-23 05:16:13 +00:00
|
|
|
{
|
|
|
|
str = str.ToLowerInvariant();
|
2021-05-03 19:26:43 +00:00
|
|
|
var now = relativeFrom ?? DateTime.Now;
|
|
|
|
|
2015-07-23 05:16:13 +00:00
|
|
|
if (str.Contains("now"))
|
2021-05-03 19:26:43 +00:00
|
|
|
return DateTime.SpecifyKind(now, DateTimeKind.Local);
|
2015-07-23 05:16:13 +00:00
|
|
|
|
2016-10-27 07:30:03 +00:00
|
|
|
str = str.Replace(",", "");
|
|
|
|
str = str.Replace("ago", "");
|
|
|
|
str = str.Replace("and", "");
|
|
|
|
|
2020-02-10 22:16:19 +00:00
|
|
|
var timeAgo = TimeSpan.Zero;
|
2020-11-04 20:56:54 +00:00
|
|
|
var timeAgoRegex = new Regex(@"\s*?([\d\.]+)\s*?([^\d\s\.]+)\s*?");
|
|
|
|
var timeAgoMatches = timeAgoRegex.Match(str);
|
2017-04-15 08:45:10 +00:00
|
|
|
|
2020-11-04 20:56:54 +00:00
|
|
|
while (timeAgoMatches.Success)
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
2020-11-04 20:56:54 +00:00
|
|
|
var val = ParseUtil.CoerceFloat(timeAgoMatches.Groups[1].Value);
|
|
|
|
var unit = timeAgoMatches.Groups[2].Value;
|
|
|
|
timeAgoMatches = timeAgoMatches.NextMatch();
|
2017-04-15 08:45:10 +00:00
|
|
|
|
2016-10-27 07:30:03 +00:00
|
|
|
if (unit.Contains("sec") || unit == "s")
|
2015-07-23 05:16:13 +00:00
|
|
|
timeAgo += TimeSpan.FromSeconds(val);
|
2016-10-27 07:30:03 +00:00
|
|
|
else if (unit.Contains("min") || unit == "m")
|
2015-07-23 05:16:13 +00:00
|
|
|
timeAgo += TimeSpan.FromMinutes(val);
|
2016-10-27 07:30:03 +00:00
|
|
|
else if (unit.Contains("hour") || unit.Contains("hr") || unit == "h")
|
2015-07-23 05:16:13 +00:00
|
|
|
timeAgo += TimeSpan.FromHours(val);
|
2020-02-09 02:35:16 +00:00
|
|
|
else if (unit.Contains("day") || unit == "d")
|
2015-07-23 05:16:13 +00:00
|
|
|
timeAgo += TimeSpan.FromDays(val);
|
2016-10-27 07:30:03 +00:00
|
|
|
else if (unit.Contains("week") || unit.Contains("wk") || unit == "w")
|
2015-07-23 05:16:13 +00:00
|
|
|
timeAgo += TimeSpan.FromDays(val * 7);
|
2016-10-27 07:30:03 +00:00
|
|
|
else if (unit.Contains("month") || unit == "mo")
|
2015-07-23 05:16:13 +00:00
|
|
|
timeAgo += TimeSpan.FromDays(val * 30);
|
2016-10-27 07:30:03 +00:00
|
|
|
else if (unit.Contains("year") || unit == "y")
|
2015-07-23 05:16:13 +00:00
|
|
|
timeAgo += TimeSpan.FromDays(val * 365);
|
|
|
|
else
|
2020-02-09 02:35:16 +00:00
|
|
|
throw new Exception("TimeAgo parsing failed, unknown unit: " + unit);
|
2015-07-23 05:16:13 +00:00
|
|
|
}
|
|
|
|
|
2021-05-03 19:26:43 +00:00
|
|
|
return DateTime.SpecifyKind(now - timeAgo, DateTimeKind.Local);
|
2015-07-23 05:16:13 +00:00
|
|
|
}
|
2016-10-27 07:30:03 +00:00
|
|
|
|
|
|
|
// Uses the DateTimeRoutines library to parse the date
|
|
|
|
// http://www.codeproject.com/Articles/33298/C-Date-Time-Parser
|
2016-12-29 18:56:18 +00:00
|
|
|
public static DateTime FromFuzzyTime(string str, string format = null)
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
2020-03-14 16:05:10 +00:00
|
|
|
var dtFormat = format == "UK" ?
|
|
|
|
DateTimeRoutines.DateTimeRoutines.DateTimeFormat.UkDate :
|
|
|
|
DateTimeRoutines.DateTimeRoutines.DateTimeFormat.UsaDate;
|
2017-04-15 08:45:10 +00:00
|
|
|
|
2020-03-14 16:05:10 +00:00
|
|
|
if (DateTimeRoutines.DateTimeRoutines.TryParseDateOrTime(
|
|
|
|
str, dtFormat, out DateTimeRoutines.DateTimeRoutines.ParsedDateTime dt))
|
2017-04-15 08:45:10 +00:00
|
|
|
return dt.DateTime;
|
2020-03-14 16:05:10 +00:00
|
|
|
|
2017-04-15 08:45:10 +00:00
|
|
|
throw new Exception("FromFuzzyTime parsing failed");
|
|
|
|
}
|
2021-05-16 18:13:54 +00:00
|
|
|
|
2021-05-03 19:26:43 +00:00
|
|
|
private static DateTime FromFuzzyPastTime(string str, string format, DateTime now)
|
|
|
|
{
|
|
|
|
var result = FromFuzzyTime(str, format);
|
|
|
|
if (result > now)
|
|
|
|
result = result.AddYears(-1);
|
|
|
|
return result;
|
|
|
|
}
|
2017-04-15 08:45:10 +00:00
|
|
|
|
2021-05-03 19:26:43 +00:00
|
|
|
public static DateTime FromUnknown(string str, string format = null, DateTime? relativeFrom = null)
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
2020-02-09 02:35:16 +00:00
|
|
|
try
|
|
|
|
{
|
2017-04-15 08:45:10 +00:00
|
|
|
str = ParseUtil.NormalizeSpace(str);
|
2021-05-03 19:26:43 +00:00
|
|
|
var now = relativeFrom ?? DateTime.Now;
|
2020-02-09 02:35:16 +00:00
|
|
|
if (str.ToLower().Contains("now"))
|
2021-05-03 19:26:43 +00:00
|
|
|
return now;
|
2017-04-15 08:45:10 +00:00
|
|
|
|
|
|
|
// ... ago
|
2020-03-14 16:05:10 +00:00
|
|
|
var match = _TimeAgoRegexp.Match(str);
|
2017-04-15 08:45:10 +00:00
|
|
|
if (match.Success)
|
|
|
|
{
|
2020-11-04 20:56:54 +00:00
|
|
|
var timeAgo = str;
|
|
|
|
return FromTimeAgo(timeAgo);
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Today ...
|
2020-03-14 16:05:10 +00:00
|
|
|
match = _TodayRegexp.Match(str);
|
2017-04-15 08:45:10 +00:00
|
|
|
if (match.Success)
|
|
|
|
{
|
|
|
|
var time = str.Replace(match.Groups[0].Value, "");
|
2021-05-03 19:26:43 +00:00
|
|
|
var dt = DateTime.SpecifyKind(now.Date, DateTimeKind.Unspecified);
|
2017-04-15 08:45:10 +00:00
|
|
|
dt += ParseTimeSpan(time);
|
|
|
|
return dt;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Yesterday ...
|
2020-03-14 16:05:10 +00:00
|
|
|
match = _YesterdayRegexp.Match(str);
|
2017-04-15 08:45:10 +00:00
|
|
|
if (match.Success)
|
|
|
|
{
|
|
|
|
var time = str.Replace(match.Groups[0].Value, "");
|
2021-05-03 19:26:43 +00:00
|
|
|
var dt = DateTime.SpecifyKind(now.Date, DateTimeKind.Unspecified);
|
2017-04-15 08:45:10 +00:00
|
|
|
dt += ParseTimeSpan(time);
|
|
|
|
dt -= TimeSpan.FromDays(1);
|
|
|
|
return dt;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tomorrow ...
|
2020-03-14 16:05:10 +00:00
|
|
|
match = _TomorrowRegexp.Match(str);
|
2017-04-15 08:45:10 +00:00
|
|
|
if (match.Success)
|
|
|
|
{
|
|
|
|
var time = str.Replace(match.Groups[0].Value, "");
|
2021-05-03 19:26:43 +00:00
|
|
|
var dt = DateTime.SpecifyKind(now.Date, DateTimeKind.Unspecified);
|
2017-04-15 08:45:10 +00:00
|
|
|
dt += ParseTimeSpan(time);
|
|
|
|
dt += TimeSpan.FromDays(1);
|
|
|
|
return dt;
|
|
|
|
}
|
|
|
|
|
2020-01-09 02:19:37 +00:00
|
|
|
// [day of the week] at ... (eg: Saturday at 14:22)
|
2020-03-14 16:05:10 +00:00
|
|
|
match = _DaysOfWeekRegexp.Match(str);
|
2020-01-06 17:34:57 +00:00
|
|
|
if (match.Success)
|
|
|
|
{
|
|
|
|
var time = str.Replace(match.Groups[0].Value, "");
|
2021-05-03 19:26:43 +00:00
|
|
|
var dt = DateTime.SpecifyKind(now.Date, DateTimeKind.Unspecified);
|
2020-01-06 17:34:57 +00:00
|
|
|
dt += ParseTimeSpan(time);
|
|
|
|
|
2020-03-14 16:05:10 +00:00
|
|
|
DayOfWeek dow;
|
2020-01-09 02:19:37 +00:00
|
|
|
var groupMatchLower = match.Groups[1].Value.ToLower();
|
2020-02-09 02:35:16 +00:00
|
|
|
if (groupMatchLower.StartsWith("monday"))
|
2020-01-06 17:34:57 +00:00
|
|
|
dow = DayOfWeek.Monday;
|
2020-02-09 02:35:16 +00:00
|
|
|
else if (groupMatchLower.StartsWith("tuesday"))
|
2020-01-06 17:34:57 +00:00
|
|
|
dow = DayOfWeek.Tuesday;
|
2020-02-09 02:35:16 +00:00
|
|
|
else if (groupMatchLower.StartsWith("wednesday"))
|
2020-01-06 17:34:57 +00:00
|
|
|
dow = DayOfWeek.Wednesday;
|
2020-02-09 02:35:16 +00:00
|
|
|
else if (groupMatchLower.StartsWith("thursday"))
|
2020-01-06 17:34:57 +00:00
|
|
|
dow = DayOfWeek.Thursday;
|
2020-02-09 02:35:16 +00:00
|
|
|
else if (groupMatchLower.StartsWith("friday"))
|
2020-01-06 17:34:57 +00:00
|
|
|
dow = DayOfWeek.Friday;
|
2020-02-09 02:35:16 +00:00
|
|
|
else if (groupMatchLower.StartsWith("saturday"))
|
2020-01-06 17:34:57 +00:00
|
|
|
dow = DayOfWeek.Saturday;
|
2020-02-09 02:35:16 +00:00
|
|
|
else
|
2020-01-06 17:34:57 +00:00
|
|
|
dow = DayOfWeek.Sunday;
|
|
|
|
|
|
|
|
while (dt.DayOfWeek != dow)
|
|
|
|
dt = dt.AddDays(-1);
|
|
|
|
return dt;
|
|
|
|
}
|
|
|
|
|
2021-05-02 02:43:24 +00:00
|
|
|
// try parsing the str as an unix timestamp
|
|
|
|
if (long.TryParse(str, out var unixTimeStamp))
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
|
|
|
return UnixTimestampToDateTime(unixTimeStamp);
|
|
|
|
}
|
2021-05-02 02:43:24 +00:00
|
|
|
// it wasn't a timestamp, continue....
|
2017-04-15 08:45:10 +00:00
|
|
|
|
|
|
|
// add missing year
|
2020-03-14 16:05:10 +00:00
|
|
|
match = _MissingYearRegexp.Match(str);
|
2017-04-15 08:45:10 +00:00
|
|
|
if (match.Success)
|
|
|
|
{
|
|
|
|
var date = match.Groups[1].Value;
|
2021-05-03 19:26:43 +00:00
|
|
|
var newDate = now.Year + "-" + date;
|
2017-04-15 08:45:10 +00:00
|
|
|
str = str.Replace(date, newDate);
|
2021-05-03 19:26:43 +00:00
|
|
|
return FromFuzzyPastTime(str, format, now);
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// add missing year 2
|
2020-03-14 16:05:10 +00:00
|
|
|
match = _MissingYearRegexp2.Match(str);
|
2017-04-15 08:45:10 +00:00
|
|
|
if (match.Success)
|
|
|
|
{
|
|
|
|
var date = match.Groups[1].Value;
|
|
|
|
var time = match.Groups[2].Value;
|
2021-05-03 19:26:43 +00:00
|
|
|
str = date + " " + now.Year + " " + time;
|
|
|
|
return FromFuzzyPastTime(str, format, now);
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return FromFuzzyTime(str, format);
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
2020-03-14 16:05:10 +00:00
|
|
|
throw new Exception($"DateTime parsing failed for \"{str}\": {ex}");
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-16 07:45:32 +00:00
|
|
|
// converts a date/time string to a DateTime object using a GoLang layout
|
2021-05-03 19:26:43 +00:00
|
|
|
public static DateTime ParseDateTimeGoLang(string date, string layout, DateTime? relativeFrom = null)
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
2021-05-03 19:26:43 +00:00
|
|
|
var now = relativeFrom ?? DateTime.Now;
|
|
|
|
|
2017-04-15 08:45:10 +00:00
|
|
|
date = ParseUtil.NormalizeSpace(date);
|
|
|
|
var pattern = layout;
|
|
|
|
|
|
|
|
// year
|
|
|
|
pattern = pattern.Replace("2006", "yyyy");
|
|
|
|
pattern = pattern.Replace("06", "yy");
|
|
|
|
|
|
|
|
// month
|
|
|
|
pattern = pattern.Replace("January", "MMMM");
|
|
|
|
pattern = pattern.Replace("Jan", "MMM");
|
|
|
|
pattern = pattern.Replace("01", "MM");
|
|
|
|
|
|
|
|
// 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");
|
|
|
|
|
|
|
|
// hours/minutes/seconds
|
|
|
|
pattern = pattern.Replace("05", "ss");
|
|
|
|
|
|
|
|
pattern = pattern.Replace("15", "HH");
|
|
|
|
pattern = pattern.Replace("03", "hh");
|
|
|
|
pattern = pattern.Replace("3", "h");
|
|
|
|
|
|
|
|
pattern = pattern.Replace("04", "mm");
|
|
|
|
pattern = pattern.Replace("4", "m");
|
|
|
|
|
|
|
|
pattern = pattern.Replace("5", "s");
|
|
|
|
|
|
|
|
// month again
|
|
|
|
pattern = pattern.Replace("1", "M");
|
|
|
|
|
|
|
|
// fractional seconds
|
|
|
|
pattern = pattern.Replace(".0000", "ffff");
|
|
|
|
pattern = pattern.Replace(".000", "fff");
|
|
|
|
pattern = pattern.Replace(".00", "ff");
|
|
|
|
pattern = pattern.Replace(".0", "f");
|
|
|
|
|
|
|
|
pattern = pattern.Replace(".9999", "FFFF");
|
|
|
|
pattern = pattern.Replace(".999", "FFF");
|
|
|
|
pattern = pattern.Replace(".99", "FF");
|
|
|
|
pattern = pattern.Replace(".9", "F");
|
|
|
|
|
|
|
|
// AM/PM
|
|
|
|
pattern = pattern.Replace("PM", "tt");
|
|
|
|
pattern = pattern.Replace("pm", "tt"); // not sure if this works
|
|
|
|
|
|
|
|
// 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");
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2021-05-03 19:26:43 +00:00
|
|
|
var dateTime = DateTime.ParseExact(date, pattern, CultureInfo.InvariantCulture);
|
|
|
|
if (!pattern.Contains("yy") && dateTime > now)
|
|
|
|
dateTime = dateTime.AddYears(-1);
|
|
|
|
return dateTime;
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
catch (FormatException ex)
|
|
|
|
{
|
2020-03-14 16:05:10 +00:00
|
|
|
throw new FormatException($"Error while parsing DateTime \"{date}\", using layout \"{layout}\" ({pattern}): {ex.Message}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-18 08:11:48 +00:00
|
|
|
private static TimeSpan ParseTimeSpan(string time) =>
|
|
|
|
string.IsNullOrWhiteSpace(time)
|
|
|
|
? TimeSpan.Zero
|
|
|
|
: DateTime.Parse(time).TimeOfDay;
|
2015-07-19 17:18:54 +00:00
|
|
|
}
|
|
|
|
}
|