Procmail filtering by Date: field - bash

I need to move away mails older than given time - let it be 24h = 86400s. I use old good procmail for multiple other purposes on that machine, so I wanted to use is as well for this purpose. It also behaves well under the load (~1 000 000 small automated messages per day).
It took me a while to get to this ugly solution (excerpt from bigger procmailrc file):
Grab Date: field using formail
Grab current date in UNIX format (seconds)
bash convert the mail date to unix format
compare values using bash
return result to procmail using exit code.
Together:
MAILDATE_RFC=`formail -zxDate:`
DATE_UNIX=`date "+%s"`
:0
* ? MAILDATE_UNIX=`date -d "$MAILDATE_RFC" "+%s"` ; if ( (( ($DATE_UNIX-$MAILDATE_UNIX) > 86400)) ) then exit 0; else exit 1; fi
! account_for_outdated_mails
In this case I need to use the "Date:" field, as this contains the local time at which the mail was generated (it can take multiple days to get to my machine). We are 100% sure that "Date:" field exists and contains RFC-style date (those are automated messages in separated mail network).
My solution looks pretty ugly:
Getting the comparison result from bash using exit codes looks pretty bad. Might be inefficient as well.
I would like to calculate the MAILDATE_RFC still in procmail but it seems I cannot use any variable as the argument to generate another variable:
MAILDATE_UNIX=`date -d "$MAILDATE_RFC" "+%s"`
does not work.
The only optimization I am aware of would be to push the whole process of getting MAILDATE_RFC, MAILDATE_UNIX and DATE_UNIX processed in bash script and doing it in one bash session instead of 3.
My question: Is there a better way to do it? Maybe more efficient?

What you say doesn't work actually does. Here's a quick demo.
testing.rc:
DEFAULT=/dev/null
SHELL=/bin/sh
VERBOSE=yes
MAILDATE_RFC=`formail -zxDate:`
MAILDATE_UNIX=`date -d "$MAILDATE_RFC" "+%s"`
NOW=`date +%s`
:0
* 86400^0 ^
* $ -$NOW^0 ^
* $ $MAILDATE_UNIX^0 ^
{ LOG="score: $=
" }
Test run, in a fresh Ubuntu 20.04 Docker image:
tripleee#bash$ procmail -m testing.rc <<\:
> Subject: demo message
> Date: Fri, 10 Jun 2022 06:20:36 +0000
>
> Try me
> :
procmail: [263] Fri Jun 10 06:21:23 2022
procmail: Executing "formail,-zxDate:"
procmail: [263] Fri Jun 10 06:21:23 2022
procmail: Assigning "MAILDATE_RFC=Fri, 10 Jun 2022 06:20:36 +0000"
procmail: Executing "date,-d,Fri, 10 Jun 2022 06:20:36 +0000,+%s"
procmail: Assigning "MAILDATE_UNIX=1654842036"
procmail: Executing "date,+%s"
procmail: Assigning "NOW=1654842083"
procmail: Score: 86400 86400 "^"
procmail: Score: -1654842083 -1654755683 "^"
procmail: Score: 1654842036 86353 "^"
procmail: Assigning "LOG=score: 86353
"
score: 86353
procmail: Assigning "LASTFOLDER=/dev/null"
procmail: Opening "/dev/null"
Folder: /dev/null 68
This also demonstrates how to use scoring to do the calculation. It's perhaps somewhat intimidating, but saves an external process, and so should be more efficient than doing the calculation in Bash.
In some more detail, 123^0 regex says to add 123 to the score just once if the message matches the regex regex (in the recipe above, we use the regex ^ which of course always matches; every message contains a beginning. You could change the 0 to e.g. 1 to say to add for every match, or etc - see the procmailsc man page for proper documentation). The $ modifier says to expand any variables in the recipe itself.
If you are not using GNU date, you don't have date -d; in that case, probably refer to your platform's man page for how to calculate a date stamp for an arbitrary date. How to convert date string to epoch timestamp with the OS X BSD `date` command? has a discussion for MacOS, which should also work for any other *BSD platform.
If you really wanted to make this more efficient, and can be sure that the Date: header really always uses the RFC-mandated format, you could even parse the date in Procmail. Something like
:0
* ^Date: [A-Z][a-z][a-z], \/[ 0-9][0-9] [A-Z][a-z][a-z] [0-9][0-9][0-9][0-9]
{
date=$MATCH
:0
* date ?? ^\/[ 0-9][0-9]
{ dd=$MATCH }
:0
* date ?? ^[ 0-9][0-9] \/[A-Z][a-z][a-z]
{ mon=$MATCH }
* date ?? [A-Z][a-z][a-z] \/[0-9][0-9][0-9][0-9]
{ yyyy=$MATCH }
:0
* mon ?? 1^0 ^Jan
* mon ?? 2^0 ^Feb
* mon ?? 3^0 ^Mar
* mon ?? 4^0 ^Apr
* mon ?? 5^0 ^May
* mon ?? 6^0 ^Jun
* mon ?? 7^0 ^Jul
* mon ?? 8^0 ^Aug
* mon ?? 9^0 ^Sep
* mon ?? 10^0 ^Oct
* mon ?? 11^0 ^Nov
* mon ?? 12^0 ^Dec
{ mm=$= }
}
The \/ token in a regex says to save the matched text after it into the special variable MATCH. We then copy that variable to date and perform additional matching to extract its parts.
Performing the necessary arithmetic to convert this into seconds since January 1, 1970 should be doable at this point, I hope. If you need complete per-day accuracy, you would also need to extract the time and the time zone and adjust to the correct day if it's not in your preferred time zone, or perhaps UTC (that would be +0000 at the very end); but this is just a sketch, anyway, because I think I have a better idea altogether.
Namely, save the messages to the correct folder as they arrive, then just forward or discard or archive older folders when you no longer need them.
MAILDATE_RFC=`formail -czxDate:`
MAILDATE=`date -d "$MAILDATE_RFC" +%F`
:0:
inbox-$MAILDATE
This will save to an mbox file named like inbox-2022-06-10 based on the extracted Date: header. (Again, you could avoid the external processes if you really wanted to squeeze out the last bit of performance, using the date parsing sketch above. And again, if you can't have a message from a different time zone land in the previous or next day's folder, you need to recalculate the date for your time zone.)

Related

MetaTrader 4 / MQL4 Time is off by -5 hours but only when using epoc time

I'm working with MetaTrader4 running in WINE in Ubuntu 16.04. I have a simple inline function that saves the time and various other info to a file using this line:
FileWrite(data_filehandle, "sys_time:" + (string)TimeLocal() + "." + StringFormat("%06lu", usec_instance) + ", sym:" + (string)Symbol() + ", tick_time:" + (string)last_tick.time + ", ask:" + (string)last_tick.ask + ", bid:" + (string)last_tick.bid);
Using the directive:
#property strict
will cause it to output the time in a Date Time format.
Removing that directive will cause it to output time in epoc format.
When it uses Date Time format ( by using '#property strict' ), the time is correct.
It outputs:
sys_time:2020.01.21 07:38:02.994394, sym:EURUSD, tick_time:2020.01.21 14:38:03, ask:1.1104, bid:1.1103
This matches my system time correctly.
Now if I remove '#property strict' to switch to epoc time
It outputs:
sys_time:1579592538.630395, sym:EURUSD, tick_time:1579617738, ask:1.1105, bid:1.11041
my local time is:
$ date '+%s'
1579610544
$ date '+%Z %z'
EST -0500
my time: 1579610544 - MT4 LocalTime: 1579592538 = 18006 seconds (Which is 5 hours and 6 sec behind me)
Any idea on what could be causing this?
I might be slightly less confused if it were +5 hours because that would be GMT.
But it's -5 hours which is Hawaii.
Also... why is the time correct in one format, but not in the other format?
Additional info
I ran some more tests using additional MQL4 functions. I had them constantly pump out their results to my text file. I then quickly whipped together a BASH script to check out the results. I found the following:
Using this code in MT4
FileWrite(data_filehandle, "TimeDaylightSavings(): " + (string)TimeDaylightSavings());
FileWrite(data_filehandle,"TimeLocal(): "+(string)TimeLocal());
FileWrite(data_filehandle,"TimeGMTOffset(): "+(string)TimeGMTOffset());
FileWrite(data_filehandle,"TimeGMT(): "+(string)TimeGMT()+"\n\n");
Gave this output to my text file ( one fresh record about every second ):
TimeDaylightSavings(): 0
TimeLocal(): 1579601184
TimeGMTOffset(): 18000
TimeGMT(): 1579619184
I whipped up this BASH script to scan and check the results in real time:
#!/bin/bash
IFS=$'\n'$'\b';
while true
do
my_time=$(date);
my_epoc=$(date '+%s');
my_record="$( cat EURUSD_price_data.txt| dos2unix | tail -5 )";
mt4_time_local=$( echo "$my_record" | grep -w 'TimeLocal' );
echo "Reading line: $mt4_time_local";
mt4_time_local=$(echo $mt4_time_local | awk '{print $2}' );
echo "My time: $my_time -- My epoc: $my_epoc -- MT4_TimeLocal epoc: $mt4_time_local -- Difference: $(( $my_epoc - $mt4_time_local ))";
mt4_time_GMT=$( echo "$my_record" | grep -w 'TimeGMT' );
echo "Reading line: $mt4_time_GMT";
mt4_time_GMT=$(echo $mt4_time_GMT | awk '{print $2}' );
echo "My time: $my_time -- My epoc: $my_epoc -- MT4_TimeGMT epoc: $mt4_time_GMT -- Difference: $(( $my_epoc - $mt4_time_GMT ))";
echo "";
sleep 1;
done
and got this result:
Reading line: TimeLocal(): 1579601184
My time: Tue Jan 21 10:06:25 EST 2020 -- My epoc: 1579619185 -- MT4_TimeLocal epoc: 1579601184 -- Difference: 18001
Reading line: TimeGMT(): 1579619184
My time: Tue Jan 21 10:06:25 EST 2020 -- My epoc: 1579619185 -- MT4_TimeGMT epoc: 1579619184 -- Difference: 1
Now if I add '#property strict' to switch back to Date Time format I get:
TimeDaylightSavings(): 0
TimeLocal(): 2020.01.21 10:23:56
TimeGMTOffset(): 18000
TimeGMT(): 2020.01.21 15:23:56
My system time:
$ date
Tue Jan 21 10:23:57 EST 2020
Conclusion
For some reason when getting epoc time TimeLocal() gives the wrong time (Hawaiian time for some reason ) , but surprisingly TimeGMT() gives the correct time, even though I am in the EST timezone.
Using the exact same code and set up, when getting the time in Date Time format ( using the '#property strict' directive ) the situation is reversed. TimeLocal() gives the correct time and TimeGMT() gives the wrong time ( but at least it gives correct GMT time )
Is this a bug in MT4, or is there something going on behind the scenes that I haven't fully understood yet?
Q : Any idea on what could be causing this?
The #property strict is a compiler-phase kill-switch, which changes lots of details how the MQL4, syntactically correct, compositions will get understood in either { "old" | "new" }-fashion
( not reading this part of the documentation each time after MT4 IDE update may and will surprise you, so better re-read it always after each and every update )
"Old"-MQL4 used a int32 for datetime internal storage, "New"-MQL4.56789… uses int64, so any roll-overs are way farther.
FileWrite( data_filehandle, "sys_time:"
+ (string)TimeLocal() // localhost-dependent
+ "."
+ StringFormat( "%06lu", usec_instance )
+ ", sym:"
+ (string)Symbol()
+ ", tick_time:"
+ (string)last_tick.time // Fx-QUOTE-dependent
+ ", ask:"
+ (string)last_tick.ask
+ ", bid:"
+ (string)last_tick.bid
);
See TimeGMT() and TimeGMTOffset() for other built-in options.
After a lot of reading, thinking and testing I have found the answer ( though I don't know what they were thinking when they did things this way ).
The issue is right here:
TimeDaylightSavings(): 0
TimeLocal(): 1579601184 <-- should be the same as TimeGMT(). Epoc does not respect timezone
TimeGMTOffset(): 18000
TimeGMT(): 1579619184
Unix epoc time is based on an event that happened on Jan 1st 1970 00:00 UTC.
It does not respect time zones. It is supposed to be the same for everyone on Earth at all times.
You can also see from the original problem that anytime I use the:
date '+%s'
command that my system gives me a correct Unix epoc which matches the output of TimeGMT() from MT4. So the problem is not my system.
However, above we can see that TimeLocal() is not treating the epoc the way that it should. It is adjusting the unix epoc in an attempt to compensate for my time zone. This behavior is probably how it accomplishes producing the correct time when it is displayed in Date Time format. The problem is that when it is asked to produce the time in epoc format it is still doing time zone conversions, which violates that very meaning of Unix epoc time.
So, the solution ( as far as I can tell ) is to simply use TimeGMT() anytime I want a correct Unix epoc. It seems pretty crazy that I have to specifically avoid using TimeLocal() in certain cases... but I guess that's just the way it is?

What is this time format? (10 digits, 5 decimals)

So a website that I'm using has a websocket and they provide the broadcast time in the following manner:
"broadcasted_at":1574325570.71308
What is this time format and how do they generate it?
Unix epoch time ... the number of seconds that have elapsed since the Unix epoch, that is the time 00:00:00 UTC on 1 January 1970
now : 1574327074 : Thu Nov 21 03:04:34 2019
start of day : 1574316000 : Thu Nov 21 00:00:00 2019
1574325570 : 1574325570 : Thu Nov 21 02:39:30 2019
convert online : https://www.epochconverter.com/
... or download code (to build) to have command line program to perform the conversion https://github.com/darrenjs/c_dev_utils
I'm guessing the fractional part is the number of microseconds within the current second.
… and how do they generate it?
I don’t know, of course, what language or libraries your website is using. So this is just an example. To generate a value like 1574325570.71308 in Java:
Instant now = Instant.now();
double epochSeconds = now.getEpochSecond()
+ (double) now.getNano() / (double) TimeUnit.SECONDS.toNanos(1);
String result = String.format(Locale.ROOT, "%f", epochSeconds);
System.out.println("result: " + result);
When I ran this snippet just now (2019-12-15T11:18:01.562699Z), the output was:
result: 1576408681.562699
If you want exactly 5 decimals always another way is to use a DateTimeFormatter:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendValue(ChronoField.INSTANT_SECONDS)
.appendPattern(".SSSSS")
.toFormatter();
String result = formatter.format(now);
result: 1576408681.56269

Create monthly trigger for Scheduled Task in Powershell (With additional criteria)

I'm currently working on a script that when run, creates some Scheduled tasks that makes the host machine do several things and then restart within a specified time span.
This script needs to be run on multiple domain controllers, and therefor i would like to "load balance" by using something like New-ScheduledTaskTrigger -RandomDelay in order for them to not reboot all at once, but kind of spread it out.
The goal is to be able to change some variables of when to restart, things like:
First Monday of the month between 18:00 and 23:59
Every Thursday between 01:00 and 06:00
Every day between 04:00 and ..... you see where I'm going
However there is no such thing as a "-Monthly" in New-ScheduledTaskTrigger
That's the first problem, this one i can probably solve with the help from other posts, but if i do it for example like this I'm not able to use the -RandomDelay which I think is a major feature for this to work.
Here is how I imagine it should look if the -Monthly did work (for a monthly trigger):
$rebootFrequency = MONTHLY # DAILY, WEEKLY, MONTHLY
$rebootWeek = FIRST # FIRST, SECOND, THIRD, FOURTH, LAST
$rebootDayOfWeek = MON # MON, TUE, WED, THU, FRI, SAT, SUN
$rebootTimeFrom = 10:00 # HH:MM[:SS]
$rebootTimeTo = 16:00 # HH:MM[:SS]
New-ScheduledTaskTrigger -"$rebootFrequency" -WeekOfMonth $rebootWeek;
-DayOfWeek $rebootDayOfWeek -At $rebootTimeFrom -RandomDelay $rebootTimeTo
Do you have any suggestions as to how I should solve this problem?
I could do the same thing with schtask.exe, however I would end up having to make some kind of script to do the "RandomDelay" function.
Feel free to ask further if you have any questions.
Thanks in advance.
Challenge 1
I've now got it to work, but I'm trying to make the script a bit more intuitive, but I can't figure out how i would do it...
What i want to do is to "convert" from using the numbers in days (for example: 16 for Thursday) to being able to write "THU" instead.
Right now it looks something like this:
$rebootDaysOfWeek = "16" # SUN=1, MON=2, TUE=4, WED=8, THU=16 etc.
$trigger.DaysOfWeek = $rebootDaysOfWeek
But I would find it alot cooler if it was something like this:
$rebootDaysOfWeek = "THU" # SUN, MON, TUE, WED, THU, FRI, SAT
$trigger.DaysOfWeek = $rebootDaysOfWeek
But I can't seem to find a way to "convert" $rebootDaysOfWeek to work with the bit mask.
Check out the Microsoft Docs:
https://learn.microsoft.com/en-us/windows/win32/taskschd/time-trigger-example--scripting-
The sample is in VB, but it looks like it's just a ComObject. I haven't had enough time to play around, but you can start like this:
$service = new-object -comobject Schedule.Service
$service.connect()
$taskdefinitiion = $service.NewTask(0)
There's lots of task definition stuff, but it get's down to the triggers and you'll do this:
$triggers = $taskDefinition.Triggers
$trigger = triggers.Create(5) # I had to try different numbers here, didn't dig through the docs
$trigger.DaysOfWeek = 16 #Thursday
$trigger.WeeksOfMonth = 1 # First week, 2 for second, 6 for third, 8 for forth
$trigger.MonthsOfYear = 4095 # all months
$trigger.RandomDelay = 'PT1H' # 1 hour random delay.
I'll let you take it from here. Links to some of the items above:
https://learn.microsoft.com/en-us/windows/win32/taskschd/monthlydowtrigger-daysofweek
https://learn.microsoft.com/en-us/windows/win32/taskschd/monthlydowtrigger-monthsofyear
https://learn.microsoft.com/en-us/windows/win32/taskschd/monthlydowtrigger-weeksofmonth
https://learn.microsoft.com/en-us/windows/win32/taskschd/monthlydowtrigger-randomdelay
UPDATE FOR CHALLENGE 1
In order to use "friendly" references to the bitwise decimal value you can either create a constants section or use hashtable, either way you are going to have to do the conversion yourself:
# Constants
$SUN = 1
$MON = 2
$TUE = 4
$WED = 8
$THU = 16
$FRI = 32
$SAT = 64
# Hashtable - because why not!
$DaysOfWeek = #{
SUN = 1
MON = 2
TUE = 4
WED = 8
THU = 16
FRI = 32
SAT = 64
}
Then you can use:
$trigger.DaysOfWeek = $THU
or
$trigger.DaysOfWeek = $DaysOfWeek["THU"]

How to use #datetime function in telegram instant view?

I am trying to set up a telegram Instant View. I am facing problem with the function datetime #datetime, I've looked at the official documentation.
I have the following date Jul 19, 2018 at 2:25pm. In case we are on the same year of the date, the string won't contain the year ex: Jul 19 at 2:25pm means 19 July of this year. How can I deal with the missing year?
This is my code so far.
#datetime(-2, "en-US", "LLL d 'at' k:mm"): "Jan 25 at 2:44pm"
published_date: $#
#manage the current year case
#datetime(0, "en-US", "LLL d, YYYY 'at' k:mma"): "Jan 25, 2018 at 2:44pm"
published_date: $#
As of now the missing year is not properly managed. In this way the year is always 1970.
The algorithm is following:
Try to parse the date with YYYY inside
If failing, the $pubslished_date will contain 0 or some garbage (try to #debug it). So you can use something like #if_not( $published_date ) { ... }, where you can try to parse the date without YYYY. Don't forget to force redefine the variable with published_date!: ….
If that won't work, try to play with conditional binding: pubslihed_date?: …. It has the same logic. (Just to put a question mark ? in the second binding in your current code).

Checking for time

I have a form field which may have a date and may have a time in it. I need to confirm that both a date and time are present.
<input type="text" name="transdate" ... />
I can use isDate(form.transdate) to check if there is a date, but it does not check if there is a time. I wish there was a isTime() function.
Addendum
The date time fields can be made to have
These fields are concatinated via
date_cat = "#form.trans_date# #form.trans_date_h#:#form.trans_date_m# #form.trans_date_t#";
When I run this code:
cat: #date_cat# isValid(date): #isValid('date', date_cat)# isValid(time): #isValid('time', date_cat)#
I get
cat: 12/05/2018 :24 PM isValid(date): YES isValid(time): YES
Some people hate regular expressions. I love them. Why not just check the concatenated string?
dtRegEx = "^(0[1-9]|1[0-2])/(0[1-9]|[1-2][0-9]|3[0-1])/[1-9][0-9]{3} (0[0-9]|1[0-2]):[0-5][0-9] (am|pm)$";
if (reFind(dtRegEx, date_cat) and isDate(date_cat)) {
// valid datetime
} else {
// invalid datetime
}
RegEx Breakdown
^
string has to start with the whole pattern
(0[1-9]|1[0-2])
month in range from 01 to 09 or 10 to 12
/
date delimiter
(0[1-9]|[1-2][0-9]|3[0-1])
day in range from 01 to 09, 10 to 29 or 30 to 31
/
date delimiter
[1-9][0-9]{3}
year in range from 1000 to 9999
space
space, literally
(0[0-9]|1[0-2])
hour in range from 00 to 09 or 10 to 12
:
time delimiter
[0-5][0-9]
seconds in range from 00 to 59
space
space, again
(am|pm)
the meridiem stuff you guys from US and UK like so much :P
$
string has to end with the whole pattern
Note that the above pattern could still have you end up with invalid day ranges like 02/31/2018, that's why you should still check with isDate().
Here is how I addressed it, I validated the fields before the concatenation
if (form.trans_date_h == "" || form.trans_date_m == "" || form.trans_date_t == "") {
// error handling here
Then did the concatenation
date_cat = "#form.trans_date# #form.trans_date_h#:#form.trans_date_m# #form.trans_date_t#";

Resources