1 /**
2   * This module contains some utils usefull for cron parser
3   *
4   * Copyright:
5   *     Copyright (c) 2018, Maxim Tyapkin.
6   * Authors:
7   *     Maxim Tyapkin
8   * License:
9   *     This software is licensed under the terms of the BSD 3-clause license.
10   *     The full terms of the license can be found in the LICENSE.md file.
11   */
12 
13 module cronexp.utils;
14 
15 private
16 {
17     import std.algorithm : each, map;
18     import std.array : array, split;
19     import std.conv : to;
20     import std.datetime;
21     import std.range : iota;
22     import std.traits : isSomeString;
23     import std.typecons : tuple;
24 }
25 
26 
27 /**
28   * Parse list of indexes to var (eg. "1,2,3,5,8")
29   */
30 void parseList(T, R)(ref T val, R expr)
31     if (isSomeString!R)
32 {
33     val = 0;
34     expr.split(",")
35         .map!(to!ubyte)
36         .array
37         .each!(n => bitSet!T(val, n));
38 }
39 
40 
41 unittest
42 {
43     ushort x;
44     parseList(x, "1,2,4,5,8");
45     assert(x == cast(ushort)0b100110110);
46 }
47 
48 
49 /**
50   * Parse range of indexes to var (eg. "2-4" or "5-1")
51   *
52   * Note:
53   * If the range "a-b" is reversed (a > b), it's mean
54   * two ranges: "a-until", "from-b"
55   */
56 void parseRange(T, R)(ref T val, R expr, ubyte from, ubyte until)
57     if (isSomeString!R)
58 {
59     val = 0;
60     auto rngs = expr.split("-")
61                     .map!(to!ubyte)
62                     .array;
63     if (rngs.length < 2)
64         return;
65 
66     if (rngs[0] < rngs[1])
67     {
68         iota(rngs[0], rngs[1] + 1)
69             .each!(n => bitSet!T(val, cast(ubyte)n));
70     }
71     else
72     {
73         iota(rngs[0], until + 1)
74             .each!(n => bitSet!T(val, cast(ubyte)n));
75         iota(from, rngs[1] + 1)
76             .each!(n => bitSet!T(val, cast(ubyte)n));
77     }
78 }
79 
80 
81 unittest
82 {
83     ushort x;
84     parseRange(x, "4-7", 1, 10);
85     assert(x == cast(ushort)0b_0000_0000_1111_0000);
86     parseRange(x, "7-4", 1, 10);
87     assert(x == cast(ushort)0b_0000_0111_1001_1110);
88 }
89 
90 
91 /**
92   * Parse sequence of indexes to var (eg. "a/x" == "a, a+x, a+2x, ...")
93   *
94   * Note: "*" is synonym for "from"
95   */
96 void parseSequence(T, R)(ref T val, R expr, ubyte from, ubyte until)
97     if (isSomeString!R)
98 {
99     val = 0;
100     auto rngs = expr.split("/")
101                     .map!(n => n == "*" ? from : n.to!ubyte)
102                     .array;
103     if(rngs.length < 2)
104         return;
105 
106     iota(rngs[0], until + 1, rngs[1])
107         .each!(n => bitSet!T(val, cast(ubyte)n));
108 }
109 
110 
111 unittest
112 {
113     ushort x;
114     parseSequence(x, "1/3", 1, 10);
115     assert(x == cast(ushort)0b_0000_0100_1001_0010);
116     parseSequence(x, "*/2", 0, 15);
117     assert(x == cast(ushort)0b_0101_0101_0101_0101);
118     parseSequence(x, "2/10", 0, 7);
119     assert(x == cast(ushort)0b_0000_0000_0000_0100);
120 }
121 
122 
123 /**
124   * Fill all allowed indexes to var
125   */
126 void parseAny(T)(ref T val, ubyte from, ubyte until)
127 {
128     val = 0;
129     iota(from, until + 1)
130         .each!(n => bitSet!T(val, cast(ubyte)n));
131 }
132 
133 
134 unittest
135 {
136     ushort x;
137     parseAny(x, 1, 10);
138     assert(x == cast(ushort)0b_0000_0111_1111_1110);
139 }
140 
141 
142 /**
143   * Get idx-th bit in var
144   */
145 bool bitTest(T)(T num, ubyte idx)
146 {
147     return (num & (cast(T)1 << idx)) != 0;
148 }
149 
150 
151 /**
152   * Set idx-th bit in var to 1
153   */
154 void bitSet(T)(ref T num, ubyte idx)
155 {
156     num = cast(T)(num | (cast(T)1 << idx));
157 }
158 
159 
160 /**
161   * Set idx-th bit in var to 0
162   */
163 void bitClear(T)(ref T num, ubyte idx)
164 {
165     num = cast(T)(num & (~(cast(T)1 << idx)));
166 }
167 
168 
169 unittest
170 {
171     ulong num = 0x1_00_00;
172     assert(bitTest(num, 16));
173     bitClear(num, 16);
174     assert(!bitTest(num, 16));
175     bitSet(num, 63);
176     assert(num);
177 }
178 
179 
180 /**
181   * Convert std.datetime.DayOfWeek enum to dow index
182   */
183 ubyte dow(DateTime dt)
184 {
185     final switch (dt.dayOfWeek) with (DayOfWeek)
186     {
187         case mon: return 1;
188         case tue: return 2;
189         case wed: return 3;
190         case thu: return 4;
191         case fri: return 5;
192         case sat: return 6;
193         case sun: return 7;
194     }
195 }
196 
197 
198 unittest
199 {
200     //3 JAN 2000 - monday
201     assert(DateTime(2000, 1, 3).dow == 1);
202     assert(DateTime(2000, 1, 4).dow == 2);
203     assert(DateTime(2000, 1, 5).dow == 3);
204     assert(DateTime(2000, 1, 6).dow == 4);
205     assert(DateTime(2000, 1, 7).dow == 5);
206     assert(DateTime(2000, 1, 8).dow == 6);
207     assert(DateTime(2000, 1, 9).dow == 7);
208 }