[feat]新增时间表达式TimeExpression,一次解析多次使用,支持${dt+1M+4d:yy-MM-dd}
This commit is contained in:
parent
6225663bff
commit
7bd9536756
|
@ -32,7 +32,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NewLife.Stardust" Version="3.0.2024.802" />
|
||||
<PackageReference Include="NewLife.Stardust" Version="3.0.2024.806" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<None Remove="Build.tt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NewLife.XCode" Version="11.15.2024.801" />
|
||||
<PackageReference Include="NewLife.XCode" Version="11.15.2024.806" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Build.log">
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NewLife.XCode" Version="11.15.2024.801" />
|
||||
<PackageReference Include="NewLife.XCode" Version="11.15.2024.806" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AntJob\AntJob.csproj" />
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NewLife.Cube.Core" Version="6.1.2024.728-beta1411" />
|
||||
<PackageReference Include="NewLife.Stardust.Extensions" Version="3.0.2024.802" />
|
||||
<PackageReference Include="NewLife.Stardust.Extensions" Version="3.0.2024.806" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AntJob.Data\AntJob.Data.csproj" />
|
||||
|
|
|
@ -49,9 +49,9 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NewLife.Core" Version="10.10.2024.801" />
|
||||
<PackageReference Include="NewLife.Remoting" Version="3.0.2024.801" />
|
||||
<PackageReference Include="NewLife.Stardust" Version="3.0.2024.802" />
|
||||
<PackageReference Include="NewLife.Core" Version="10.10.2024.803" />
|
||||
<PackageReference Include="NewLife.Remoting" Version="3.0.2024.805" />
|
||||
<PackageReference Include="NewLife.Stardust" Version="3.0.2024.806" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -21,20 +21,20 @@ public static class TemplateHelper
|
|||
while (true)
|
||||
{
|
||||
var ti = Find(str, "DataTime", p);
|
||||
ti ??= Find(str, "dt", p);
|
||||
if (ti == null)
|
||||
if (ti.IsEmpty) ti = Find(str, "dt", p);
|
||||
if (ti.IsEmpty)
|
||||
{
|
||||
sb.Append(str.Substring(p));
|
||||
break;
|
||||
}
|
||||
|
||||
// 准备替换
|
||||
var val = ti.Item3.IsNullOrEmpty() ? startTime.ToFullString() : startTime.ToString(ti.Item3);
|
||||
sb.Append(str.Substring(p, ti.Item1 - p));
|
||||
var val = ti.Format.IsNullOrEmpty() ? startTime.ToFullString() : startTime.ToString(ti.Format);
|
||||
sb.Append(str.Substring(p, ti.Start - p));
|
||||
sb.Append(val);
|
||||
|
||||
// 移动指针
|
||||
p = ti.Item2 + 1;
|
||||
p = ti.End + 1;
|
||||
}
|
||||
|
||||
str = sb.ToString();
|
||||
|
@ -43,32 +43,32 @@ public static class TemplateHelper
|
|||
while (true)
|
||||
{
|
||||
var ti = Find(str, "End", p);
|
||||
if (ti == null)
|
||||
if (ti.IsEmpty)
|
||||
{
|
||||
sb.Append(str.Substring(p));
|
||||
break;
|
||||
}
|
||||
|
||||
// 准备替换
|
||||
var val = ti.Item3.IsNullOrEmpty() ? endTime.ToFullString() : endTime.ToString(ti.Item3);
|
||||
sb.Append(str.Substring(p, ti.Item1 - p));
|
||||
var val = ti.Format.IsNullOrEmpty() ? endTime.ToFullString() : endTime.ToString(ti.Format);
|
||||
sb.Append(str.Substring(p, ti.Start - p));
|
||||
sb.Append(val);
|
||||
|
||||
// 移动指针
|
||||
p = ti.Item2 + 1;
|
||||
p = ti.End + 1;
|
||||
}
|
||||
|
||||
return sb.Put(true);
|
||||
}
|
||||
|
||||
private static Tuple<Int32, Int32, String> Find(String str, String key, Int32 p)
|
||||
private static VarItem Find(String str, String key, Int32 p)
|
||||
{
|
||||
// 头尾
|
||||
var p1 = str.IndexOf("{" + key, p);
|
||||
if (p1 < 0) return null;
|
||||
if (p1 < 0) return _empty;
|
||||
|
||||
var p2 = str.IndexOf("}", p1);
|
||||
if (p2 < 0) return null;
|
||||
if (p2 < 0) return _empty;
|
||||
|
||||
// 格式化字符串
|
||||
var format = "";
|
||||
|
@ -76,7 +76,17 @@ public static class TemplateHelper
|
|||
if (p3 > 0 && p3 < p2) format = str.Substring(p3 + 1, p2 - p3 - 1);
|
||||
|
||||
// 左括号位置,右括号位置,格式化字符串
|
||||
return new Tuple<Int32, Int32, String>(p1, p2, format);
|
||||
return new VarItem(p1, p2, format);
|
||||
}
|
||||
|
||||
private static VarItem _empty = new(-1, -1, "");
|
||||
struct VarItem(Int32 start, Int32 end, String format)
|
||||
{
|
||||
public Int32 Start = start;
|
||||
public Int32 End = end;
|
||||
public String Format = format;
|
||||
|
||||
public readonly Boolean IsEmpty => Start < 0;
|
||||
}
|
||||
|
||||
/// <summary>使用消息数组处理模板</summary>
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
using NewLife;
|
||||
|
||||
namespace AntJob.Data;
|
||||
|
||||
/// <summary>时间表达式,一次解析多次使用。如{dt+1M+5d:yyyyMMdd}</summary>
|
||||
public class TimeExpression
|
||||
{
|
||||
#region 属性
|
||||
/// <summary>表达式</summary>
|
||||
public String Expression { get; set; }
|
||||
|
||||
/// <summary>变量名</summary>
|
||||
public String VarName { get; set; } = "dt";
|
||||
|
||||
/// <summary>格式化字符串</summary>
|
||||
public String Format { get; set; }
|
||||
|
||||
/// <summary>时间表达式项集合</summary>
|
||||
public IList<TimeExpressionItem> Items { get; } = [];
|
||||
#endregion
|
||||
|
||||
#region 构造
|
||||
/// <summary>实例化时间表达式</summary>
|
||||
public TimeExpression() { }
|
||||
|
||||
/// <summary>实例化时间表达式</summary>
|
||||
public TimeExpression(String expression) => Parse(expression);
|
||||
#endregion
|
||||
|
||||
#region 方法
|
||||
/// <summary>解析表达式</summary>
|
||||
public Boolean Parse(String expression)
|
||||
{
|
||||
var p1 = expression.IndexOf('{');
|
||||
if (p1 < 0) return false;
|
||||
|
||||
var p2 = expression.IndexOf('}', p1);
|
||||
if (p2 < 0) return false;
|
||||
|
||||
expression = expression.Substring(p1 + 1, p2 - p1 - 1);
|
||||
|
||||
// 循环查找
|
||||
var ms = Items;
|
||||
p1 = -1;
|
||||
while (true)
|
||||
{
|
||||
p2 = expression.IndexOfAny(['+', '-', ':', ','], p1 + 1);
|
||||
if (p2 < 0) p2 = expression.Length;
|
||||
|
||||
// 第一段是变量名
|
||||
if (p1 < 0 && p2 > 0)
|
||||
{
|
||||
VarName = expression[0..p2];
|
||||
}
|
||||
else if (expression[p1] is '+' or '-')
|
||||
{
|
||||
var str = expression[p1..p2];
|
||||
var item = new TimeExpressionItem();
|
||||
if (!item.Parse(str)) throw new InvalidDataException($"Invalid [{str}]");
|
||||
|
||||
ms.Add(item);
|
||||
}
|
||||
else if (expression[p1] is ':' or ',')
|
||||
{
|
||||
// 最后一段是格式化字符串
|
||||
p2 = expression.Length;
|
||||
Format = expression[(p1 + 1)..p2];
|
||||
}
|
||||
|
||||
if (p2 >= expression.Length) break;
|
||||
p1 = p2;
|
||||
}
|
||||
|
||||
// 默认天级
|
||||
if (ms.Count == 0) ms.Add(new TimeExpressionItem { Level = "d", Value = 0 });
|
||||
|
||||
Expression = expression;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>执行时间偏移</summary>
|
||||
public DateTime Execute(DateTime time)
|
||||
{
|
||||
foreach (var item in Items)
|
||||
{
|
||||
time = item.Execute(time);
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
/// <summary>构建时间字符串</summary>
|
||||
public String Build(DateTime time)
|
||||
{
|
||||
time = Execute(time);
|
||||
|
||||
var ms = Items;
|
||||
var format = Format;
|
||||
if (format.IsNullOrEmpty() && ms.Count > 0) format = ms[ms.Count - 1].GetFormat();
|
||||
if (format.IsNullOrEmpty()) format = "yyyyMMdd";
|
||||
|
||||
return time.ToString(format);
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>处理时间偏移模版。如{dt+1M+5d:yyyyMMdd}</summary>
|
||||
/// <param name="template"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
public static String Build(String template, DateTime time)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>时间表达式项。如+5d</summary>
|
||||
public class TimeExpressionItem
|
||||
{
|
||||
/// <summary>级别。如y/M/d/H/m/w</summary>
|
||||
public String Level { get; set; }
|
||||
|
||||
/// <summary>数值。包括正负</summary>
|
||||
public Int32 Value { get; set; }
|
||||
|
||||
/// <summary>分解表达式项。如+5d</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public Boolean Parse(String value)
|
||||
{
|
||||
if (value.IsNullOrEmpty() || value.Length < 3) return false;
|
||||
|
||||
Level = value[^1..];
|
||||
Value = value[..^1].ToInt();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>执行时间偏移</summary>
|
||||
public DateTime Execute(DateTime time)
|
||||
{
|
||||
return Level switch
|
||||
{
|
||||
"y" => new DateTime(time.Year, 1, 1, 0, 0, 0, time.Kind).AddYears(Value),
|
||||
"M" => new DateTime(time.Year, time.Month, 1, 0, 0, 0, time.Kind).AddMonths(Value),
|
||||
"d" => new DateTime(time.Year, time.Month, time.Day, 0, 0, 0, time.Kind).AddDays(Value),
|
||||
"H" => new DateTime(time.Year, time.Month, time.Day, time.Hour, 0, 0, time.Kind).AddHours(Value),
|
||||
"m" => new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, 0, time.Kind).AddMinutes(Value),
|
||||
"s" => new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second, time.Kind).AddSeconds(Value),
|
||||
"w" => new DateTime(time.Year, time.Month, time.Day, 0, 0, 0, time.Kind).AddDays(Value * 7),
|
||||
_ => time,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>获取格式化字符串</summary>
|
||||
public String GetFormat()
|
||||
{
|
||||
return Level switch
|
||||
{
|
||||
"y" => "yyyy",
|
||||
"M" => "yyyyMM",
|
||||
"d" => "yyyyMMdd",
|
||||
"H" => "yyyyMMddHH",
|
||||
"m" => "yyyyMMddHHmm",
|
||||
"s" => "yyyyMMddHHmmss",
|
||||
"w" => "yyyyww",
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using AntJob.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace AntTest;
|
||||
|
||||
public class TimeExpressionTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("+1y", "y", 1)]
|
||||
[InlineData("-1y", "y", -1)]
|
||||
[InlineData("+5M", "M", 5)]
|
||||
[InlineData("-5M", "M", -5)]
|
||||
[InlineData("+5d", "d", 5)]
|
||||
[InlineData("-5d", "d", -5)]
|
||||
[InlineData("-0d", "d", 0)]
|
||||
[InlineData("+5H", "H", 5)]
|
||||
[InlineData("-5H", "H", -5)]
|
||||
[InlineData("+5m", "m", 5)]
|
||||
[InlineData("-5m", "m", -5)]
|
||||
[InlineData("+5w", "w", 5)]
|
||||
[InlineData("-5w", "w", -5)]
|
||||
public void ParseItem(String str, String level, Int32 value)
|
||||
{
|
||||
var item = new TimeExpressionItem();
|
||||
var rs = item.Parse(str);
|
||||
Assert.True(rs);
|
||||
Assert.Equal(level, item.Level);
|
||||
Assert.Equal(value, item.Value);
|
||||
|
||||
var time = DateTime.Now;
|
||||
var time2 = item.Execute(time);
|
||||
|
||||
if (value > 0)
|
||||
Assert.True(time2 > time);
|
||||
else if (value < 0)
|
||||
Assert.True(time2 < time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDefault()
|
||||
{
|
||||
var exp = new TimeExpression("${dt}");
|
||||
Assert.Equal("dt", exp.Expression);
|
||||
Assert.Equal("dt", exp.VarName);
|
||||
Assert.Null(exp.Format);
|
||||
Assert.Single(exp.Items);
|
||||
|
||||
var time = DateTime.Now;
|
||||
var time2 = exp.Execute(time);
|
||||
Assert.Equal(time.Date, time2);
|
||||
|
||||
var rs = exp.Build(time);
|
||||
Assert.Equal(time.ToString("yyyyMMdd"), rs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test2()
|
||||
{
|
||||
var exp = new TimeExpression("${dt+2d}");
|
||||
Assert.Equal("dt+2d", exp.Expression);
|
||||
Assert.Equal("dt", exp.VarName);
|
||||
Assert.Null(exp.Format);
|
||||
Assert.Single(exp.Items);
|
||||
|
||||
var time = DateTime.Now;
|
||||
var time2 = exp.Execute(time);
|
||||
var time3 = time.Date.AddDays(2);
|
||||
Assert.Equal(time3, time2);
|
||||
|
||||
var rs = exp.Build(time);
|
||||
Assert.Equal(time3.ToString("yyyyMMdd"), rs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test3()
|
||||
{
|
||||
var exp = new TimeExpression("${dt-3H:yyMMddHH}");
|
||||
Assert.Equal("dt-3H:yyMMddHH", exp.Expression);
|
||||
Assert.Equal("dt", exp.VarName);
|
||||
Assert.Equal("yyMMddHH", exp.Format);
|
||||
Assert.Single(exp.Items);
|
||||
|
||||
var time = DateTime.Now;
|
||||
var time2 = exp.Execute(time);
|
||||
var time3 = time.Date.AddHours(time.Hour - 3);
|
||||
Assert.Equal(time3, time2);
|
||||
|
||||
var rs = exp.Build(time);
|
||||
Assert.Equal(time3.ToString("yyMMddHH"), rs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test4()
|
||||
{
|
||||
var exp = new TimeExpression("${dt+1M+4d:yy-MM-dd}");
|
||||
Assert.Equal("dt+1M+4d:yy-MM-dd", exp.Expression);
|
||||
Assert.Equal("dt", exp.VarName);
|
||||
Assert.Equal("yy-MM-dd", exp.Format);
|
||||
Assert.Equal(2, exp.Items.Count);
|
||||
|
||||
var time = DateTime.Now;
|
||||
var time2 = exp.Execute(time);
|
||||
var time3 = time.Date.AddDays(1 - time.Day).AddMonths(1).AddDays(4);
|
||||
Assert.Equal(time3, time2);
|
||||
|
||||
var rs = exp.Build(time);
|
||||
Assert.Equal(time3.ToString("yy-MM-dd"), rs);
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NewLife.XCode" Version="11.15.2024.801" />
|
||||
<PackageReference Include="NewLife.XCode" Version="11.15.2024.806" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
Loading…
Reference in New Issue