Files
duplicati/Duplicati/Library/Utility/Timeparser.cs
T

209 lines
9.3 KiB
C#

// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
namespace Duplicati.Library.Utility
{
/// <summary>
/// Utility class to parse date/time offset strings like duplicity does:
/// http://duplicity.nongnu.org/vers8/duplicity.1.html#sect8
/// </summary>
public static class Timeparser
{
public static TimeSpan ParseTimeSpan(string datestring)
=> ParseTimeSpan(datestring, false);
public static TimeSpan ParseTimeSpan(string datestring, bool negate)
{
var multiplier = negate ? -1 : 1;
var offset = TimeSpan.Zero;
if (string.IsNullOrEmpty(datestring))
return offset;
if (long.TryParse(datestring, System.Globalization.NumberStyles.Integer, null, out var l))
return offset.Add(TimeSpan.FromSeconds(l * multiplier));
var separators = new char[] { 's', 'm', 'h', 'D', 'W', 'M', 'Y' };
int index;
var previndex = 0;
while ((index = datestring.IndexOfAny(separators, previndex)) > 0)
{
var partial = datestring.Substring(previndex, index - previndex).Trim();
if (!int.TryParse(partial, System.Globalization.NumberStyles.Integer, null, out var factor))
throw new Exception(Strings.Timeparser.InvalidIntegerError(partial));
factor *= multiplier;
offset += datestring[index] switch
{
's' => TimeSpan.FromSeconds(factor),
'm' => TimeSpan.FromMinutes(factor),
'h' => TimeSpan.FromHours(factor),
'D' => TimeSpan.FromDays(factor),
'W' => TimeSpan.FromDays(factor * 7),
'M' => TimeSpan.FromDays(factor * 30),
'Y' => TimeSpan.FromDays(factor * 365),
_ => throw new Exception(Strings.Timeparser.InvalidSpecifierError(datestring[index])),
};
previndex = index + 1;
}
if (datestring.Substring(previndex).Trim().Length > 0)
throw new Exception(Strings.Timeparser.UnparsedDataFragmentError(datestring.Substring(previndex)));
return offset;
}
public static DateTime ParseTimeInterval(string datestring, DateTime offset)
{
return ParseTimeInterval(datestring, offset, false);
}
public static DateTime ParseTimeInterval(string datestring, DateTime offset, bool negate)
{
if (offset.Kind == DateTimeKind.Unspecified)
offset = new DateTime(offset.Ticks, DateTimeKind.Local);
var multiplier = negate ? -1 : 1;
if (string.IsNullOrEmpty(datestring))
return offset;
if (string.Equals(datestring.Trim(), "now", StringComparison.OrdinalIgnoreCase))
return DateTime.Now;
if (long.TryParse(datestring, System.Globalization.NumberStyles.Integer, null, out var l))
return offset.AddSeconds(l * multiplier);
if (DateTime.TryParse(datestring, System.Globalization.CultureInfo.CurrentCulture, System.Globalization.DateTimeStyles.AssumeLocal, out var t))
return t;
if (Utility.TryDeserializeDateTime(datestring, out t))
return t;
var separators = new char[] { 's', 'm', 'h', 'D', 'W', 'M', 'Y' };
int index;
var previndex = 0;
while ((index = datestring.IndexOfAny(separators, previndex)) > 0)
{
var partial = datestring.Substring(previndex, index - previndex).Trim();
if (!int.TryParse(partial, System.Globalization.NumberStyles.Integer, null, out var factor))
throw new Exception(Strings.Timeparser.InvalidIntegerError(partial));
factor *= multiplier;
offset = datestring[index] switch
{
's' => offset.AddSeconds(factor),
'm' => offset.AddMinutes(factor),
'h' => offset.AddHours(factor),
'D' => offset.AddDays(factor),
'W' => offset.AddDays(factor * 7),
'M' => offset.AddMonths(factor),
'Y' => offset.AddYears(factor),
_ => throw new Exception(Strings.Timeparser.InvalidSpecifierError(datestring[index])),
};
previndex = index + 1;
}
if (datestring.Substring(previndex).Trim().Length > 0)
throw new Exception(Strings.Timeparser.UnparsedDataFragmentError(datestring.Substring(previndex)));
return offset;
}
/// <summary>
/// Parses a time interval string with a timezone offset, retaining the local time
/// </summary>
/// <param name="datestring">The repeating interval string</param>
/// <param name="offset">The base time to add the interval to</param>
/// <param name="timeZoneInfo">The timezone to use for the calculation</param>
/// <param name="keepTimeOfDay">True if the time of day should be kept across DST changes</param>
/// <param name="negate">True if the interval should be subtracted</param>
/// <returns>The calculated time</returns>
public static DateTime DSTAwareParseTimeInterval(string datestring, DateTime offset, TimeZoneInfo timeZoneInfo, bool keepTimeOfDay, bool negate = false)
{
// Use non-DST-aware version if we are not fixing this to the current time-of-day
if (!keepTimeOfDay)
return ParseTimeInterval(datestring, offset, negate);
if (offset.Kind == DateTimeKind.Unspecified)
offset = new DateTime(offset.Ticks, DateTimeKind.Utc);
int multiplier = negate ? -1 : 1;
if (string.IsNullOrEmpty(datestring))
return offset;
if (string.Equals(datestring.Trim(), "now", StringComparison.OrdinalIgnoreCase))
return DateTime.UtcNow;
if (long.TryParse(datestring, System.Globalization.NumberStyles.Integer, null, out var l))
return timeZoneInfo.DSTAwareAddSeconds(offset, l * multiplier);
if (DateTime.TryParse(datestring, System.Globalization.CultureInfo.CurrentCulture, System.Globalization.DateTimeStyles.AssumeLocal, out var t))
return t;
if (Utility.TryDeserializeDateTime(datestring, out t))
return t;
var separators = new char[] { 's', 'm', 'h', 'D', 'W', 'M', 'Y' };
int index;
var previndex = 0;
while ((index = datestring.IndexOfAny(separators, previndex)) > 0)
{
var partial = datestring.Substring(previndex, index - previndex).Trim();
if (!int.TryParse(partial, System.Globalization.NumberStyles.Integer, null, out var factor))
throw new Exception(Strings.Timeparser.InvalidIntegerError(partial));
factor *= multiplier;
offset = datestring[index] switch
{
's' => timeZoneInfo.DSTAwareAddSeconds(offset, factor),
'm' => timeZoneInfo.DSTAwareAddMinutes(offset, factor),
'h' => timeZoneInfo.DSTAwareAddHours(offset, factor),
'D' => timeZoneInfo.DSTAwareAddDays(offset, factor),
'W' => timeZoneInfo.DSTAwareAddDays(offset, factor * 7),
'M' => timeZoneInfo.DSTAwareAddMonths(offset, factor),
'Y' => timeZoneInfo.DSTAwareAddYears(offset, factor),
_ => throw new Exception(Strings.Timeparser.InvalidSpecifierError(datestring[index])),
};
previndex = index + 1;
}
if (datestring.Substring(previndex).Trim().Length > 0)
throw new Exception(Strings.Timeparser.UnparsedDataFragmentError(datestring.Substring(previndex)));
return offset;
}
}
}