fetch date and covert into weekday - bash

i have data below in csv
Date
2022-06-09 22:30:20
2022-06-10 15:55:21
2022-06-11 00:34:05
2022-06-11 19:51:52
2022-06-13 11:34:10
2022-06-15 03:59:54
2022-06-18 16:13:20
2022-06-19 00:24:21
2022-06-19 00:25:36
2022-06-19 00:25:36
2022-06-19 00:25:49
i required output in 2 fields as weekday and shift time, if hh:mm is between 7:30AM to 7:30PM it should be print as morning, remaining will be print as Night.
date | Weekday | Shift
--------------------------------------------------------------
09-06-2022 22:30 | Thursday | Night
10-06-2022 15:55 | Friday | Morning
11-06-2022 00:34 | Saturday | Night
11-06-2022 19:51 | Saturday | Night
13-06-2022 11:34 | Monday | Morning
15-06-2022 03:59 | Wednesday | Night
18-06-2022 16:13 | Saturday | Morning
19-06-2022 00:24 | Sunday | Night
19-06-2022 00:25 | Sunday | Night
19-06-2022 00:25 | Sunday | Night
19-06-2022 00:25 | Sunday | Night
I tried with below command to get weekdays and facing difficulties in shift column please help
date --date="$dates" +%A

Using GNU awk:
gawk 'function dayofweek(time) {
gsub(/[:-]/, " ", time)
return strftime("%A", mktime(time));
}
BEGIN { OFS="," }
NR == 1 { print "Date", "Weekday", "Shift"; next }
{
print substr($0, 0, length($0) - 3), dayofweek($0), $2 >= "07:30:00" && $2 <= "19:30:00" ? "Morning" : "Night"
}' input.csv
produces
Date,Weekday,Shift
2022-06-09 22:30,Thursday,Night
2022-06-10 15:55,Friday,Morning
2022-06-11 00:34,Saturday,Night
2022-06-11 19:51,Saturday,Night
2022-06-13 11:34,Monday,Morning
2022-06-15 03:59,Wednesday,Night
2022-06-18 16:13,Saturday,Morning
2022-06-19 00:24,Sunday,Night
2022-06-19 00:25,Sunday,Night
2022-06-19 00:25,Sunday,Night
2022-06-19 00:25,Sunday,Night
from your input.
It trims the seconds from the date, uses GNU awk specific functions mktime() and strftime() to get the weekday from the time, and finally just compares the hours portion to the desired range to see if it's morning or night.

fetch date and covert into weekday
I would use GNU AWK for this task following way, let file.txt context be
Date
2022-06-09 22:30:20
2022-06-10 15:55:21
2022-06-11 00:34:05
2022-06-11 19:51:52
2022-06-13 11:34:10
2022-06-15 03:59:54
2022-06-18 16:13:20
2022-06-19 00:24:21
2022-06-19 00:25:36
2022-06-19 00:25:36
2022-06-19 00:25:49
then
awk 'BEGIN{FS="-| |:"}NR==1{print "Date","Weekday"}NR>1{t=mktime($1 " " $2 " " $3 " " $4 " " $5 " " $6);print $0,strftime("%A",t)}' file.txt
gives output
Date Weekday
2022-06-09 22:30:20 Thursday
2022-06-10 15:55:21 Friday
2022-06-11 00:34:05 Saturday
2022-06-11 19:51:52 Saturday
2022-06-13 11:34:10 Monday
2022-06-15 03:59:54 Wednesday
2022-06-18 16:13:20 Saturday
2022-06-19 00:24:21 Sunday
2022-06-19 00:25:36 Sunday
2022-06-19 00:25:36 Sunday
2022-06-19 00:25:49 Sunday
Explanation: firstly I inform GNU AWK that field separator is - or (space) or :, then for 1st line I print header, for all lines after 1st I use Time Functions, mktime converts string like YYYY MM DD HH MM SS into timestamp (number of seconds since start of epoch), then I use strftime to convert said variable into string, %A denotes full weekday name.
(tested in gawk 4.2.1)

With awk (tested with GNU awk):
$ awk '
BEGIN {
sep = sprintf("%41s", " ")
gsub(/ /, "-", sep);
printf("%-19s | %-9s | %-10s\n%s\n", "Day", "Weekday", "Shift", sep)
mmin = 7 * 3600 + 30 * 60
mmax = 19 * 3600 + 30 * 60
}
NR > 1 {
dt = $0
gsub(/-|:/, " ", dt)
s = mktime(dt)
dt0 = $1 " 00 00 00"
gsub(/-/, " ", dt0)
s0 = mktime(dt0)
d = s - s0
shift = (d > mmin && d < mmax) ? "Morning" : "Night"
printf("%-19s | %-9s | %s\n", $0, strftime("%A", s), shift)
}' file
Day | Weekday | Shift
-----------------------------------------
2022-06-09 22:30:20 | Thursday | Night
2022-06-10 15:55:21 | Friday | Morning
2022-06-11 00:34:05 | Saturday | Night
2022-06-11 19:51:52 | Saturday | Night
2022-06-13 11:34:10 | Monday | Morning
2022-06-15 03:59:54 | Wednesday | Night
2022-06-18 16:13:20 | Saturday | Morning
2022-06-19 00:24:21 | Sunday | Night
2022-06-19 00:25:36 | Sunday | Night
2022-06-19 00:25:36 | Sunday | Night
2022-06-19 00:25:49 | Sunday | Night
mmin is 7:30 AM in seconds. mmax is 7:30 PM in seconds. dt is the input date - time with all - and : replaced by a space (this is the input format of mktime). s is dt converted to seconds since Epoch using mktime. dt0 and s0 are the same as dtand s but at 00:00:00. d is the time in seconds since 00:00:00. The rest is straightforward.

awk '
NR==1{
printf "%-16s | %-9s | %s\n", "Date","Weekday","Shift"; next
}
{
"date -d \"" $0 "\" \"+%d-%m-%Y %H:%M | %A\"" | getline d
gsub(/:/, "", $2); t=int($2)
printf "%-28s | %s\n", d ,(t > 73000 && t < 193000) ? "Morning" : "Night"
}' file.csv
Date | Weekday | Shift
09-06-2022 22:30 | Thursday | Night
10-06-2022 15:55 | Friday | Morning
11-06-2022 00:34 | Saturday | Night
11-06-2022 19:51 | Saturday | Night
13-06-2022 11:34 | Monday | Morning
15-06-2022 03:59 | Wednesday | Night
18-06-2022 16:13 | Saturday | Morning
19-06-2022 00:24 | Sunday | Night
19-06-2022 00:25 | Sunday | Night
19-06-2022 00:25 | Sunday | Night
19-06-2022 00:25 | Sunday | Night

With text and string and csv support, ruby is my go to for such projects:
ruby -r csv -e '
options={ :headers=>true, :converters=>:date_time}
def d_or_n(dt)
t=dt.strftime( "%H%M%S%N" )
st=DateTime.new(2000,1,1,7,30).strftime( "%H%M%S%N" )
et=DateTime.new(2000,1,1,19,30).strftime( "%H%M%S%N" )
t >= st && t <= et ? "Day" : "Night"
end
cols=[18,12,7]
fmt="%*s|%*s|%*s\n"
printf(fmt,cols[0],"Date".center(cols[0]),
cols[1],"Weekday".center(cols[1]), cols[2], "Shift".center(cols[2]))
printf("-"*(cols.sum+2)+"\n")
inp=CSV.parse($<.read, **options).to_a
inp[1..].each{|r| printf(fmt, cols[0], r[0].strftime("%d-%m-%Y %R "),
cols[1], r[0].strftime("%A "),
cols[2], d_or_n(r[0])) }
' dates.csv
Prints:
Date | Weekday | Shift
---------------------------------------
09-06-2022 22:30 | Thursday | Night
10-06-2022 15:55 | Friday | Day
11-06-2022 00:34 | Saturday | Night
11-06-2022 19:51 | Saturday | Night
13-06-2022 11:34 | Monday | Day
15-06-2022 03:59 | Wednesday | Night
18-06-2022 16:13 | Saturday | Day
19-06-2022 00:24 | Sunday | Night
19-06-2022 00:25 | Sunday | Night
19-06-2022 00:25 | Sunday | Night
19-06-2022 00:25 | Sunday | Night

using system() is far more light-weight than a getline :
{m,g}awk '($++NF=substr("SunMonTueWedThuFriSat",1+3 * \
system("exit \140 gdate -d\42"($_)" UTC-4\42 +%w \140"), 3)) \
($++NF=substr("MorngNight",6^($2~/^(2|0[0-6]|07:[0-2]|19:[^0-2])/),5))
2022-06-09 22:30:20 Thu Night
2022-06-10 15:55:21 Fri Morng
2022-06-11 00:34:05 Sat Night
2022-06-11 19:51:52 Sat Night
2022-06-13 11:34:10 Mon Morng
2022-06-15 03:59:54 Wed Night
2022-06-18 16:13:20 Sat Morng
2022-06-19 00:24:21 Sun Night
2022-06-19 00:25:36 Sun Night
2022-06-19 00:25:36 Sun Night
2022-06-19 00:25:49 Sun Night

Related

Alphanumeric date to numeric in shell

I have a data file where dates are alphanumeric at each 10 minutes. e.g.
00 hour 00 minute (00:00H)
00 hour 10 minute (00:10H)
00 hour 20 minute (00:20H)
and so on
$ ifile.txt
00:00H01JUN2021 1.900
00:10H01JUN2021 2.400
00:20H01JUN2021 2.100
00:30H01JUN2021 2.300
00:40H01JUN2021 2.00
00:50H01JUN2021 2.300
01:00H01JUN2021 2.300
01:10H01JUN2021 0.000
01:20H01JUN2021 2.200
01:30H01JUN2021 0.100
To understand the data:
1st column is date; second column is the value at that time
First 6 letters YY:XXH indicats as YY -> Hour; XX -> Minute (as explained in the begining)
I would like to convert it into a CSV file with numeric dates. The desire outfile is
$ ofile.txt
yyyy-mm-dd hh-mn-sc,val
2021-06-01 00:00:00,1.900
2021-06-01 00:10:00,2.400
2021-06-01 00:20:00,2.100
2021-06-01 00:30:00,2.300
2021-06-01 00:40:00,2.000
2021-06-01 00:50:00,2.300
2021-06-01 01:00:00,2.300
2021-06-01 01:10:00,0.000
2021-06-01 01:20:00,2.200
2021-06-01 01:30:00,0.100
My script is:
#!/bin/sh
gawk '
BEGIN {
month["Jan"] = "01"; month["Feb"] = "02"; month["Mar"] = "03";
month["Apr"] = "04"; month["May"] = "05"; month["Jun"] = "06";
month["Jul"] = "07"; month["Aug"] = "08"; month["Sep"] = "09";
month["Oct"] = "10"; month["Nov"] = "11"; month["Dec"] = "12";
}
function timestamp_to_numeric(s) {
# 00:00H01JUN2021 => 2021-06-01 00:00:00
return substr(s,12,4)"-"month[substr(s,9,3)]"-"substr(s,7,2) substr(s,1,2)":"substr(s,4,2)":""00"
}
NR==1 {next}
END {
printf "%s",timestamp_to_numeric($1),$2
printf "\n"
}
' ifile.txt
This script is not priniting my desired output.
Using GNU awk (since you're already using it) for the 4th arg to split():
$ cat tst.awk
function timestamp_to_numeric(s, mthNr,t,m) {
# 00:00H01JUN2021 => 2021-06-01 00:00:00
split(s,t,/[[:alpha:]]+/,m)
mthNr = index(" JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC",m[2]) / 3
return sprintf("%04d-%02d-%02d %s:00", t[3], mthNr, t[2], t[1])
}
BEGIN {
OFS=","
print "yyyy-mm-dd hh-mn-sc","val"
}
{ print timestamp_to_numeric($1), $2 }
$ awk -f tst.awk ifile.txt
yyyy-mm-dd hh-mn-sc,val
2021-06-01 00:00:00,1.900
2021-06-01 00:10:00,2.400
2021-06-01 00:20:00,2.100
2021-06-01 00:30:00,2.300
2021-06-01 00:40:00,2.00
2021-06-01 00:50:00,2.300
2021-06-01 01:00:00,2.300
2021-06-01 01:10:00,0.000
2021-06-01 01:20:00,2.200
2021-06-01 01:30:00,0.100
Change
return substr(s,12,4)"-"month[substr(s,9,3)]"-"substr(s,7,2) substr(s,1,2)":"substr(s,4,2)":""00"
to
return substr(s,12,4)"-"month[substr(s,9,3)]"-"substr(s,7,2)" "substr(s,1,2)":"substr(s,4,2)":""00"
# .................................,........................^^^
so that you actually have a space between the date and the time.
Perhaps more readable would be:
return sprintf("%4d-%02d-%02d %02d:%02d:00", substr(s,12,4), month[substr(s,9,3)], substr(s,7,2), substr(s,1,2), substr(s,4,2))
To put it together in awk, without being dependent on gawk to get seps from array-splitting (confirmed working on mawk | gawk | nawk) :
echo "${...input_data...}" | awk '
function ____(__,_,___) {
return \
sprintf((_ = (_ = "%s%.*d")_)(_)_,
_ = ___, (_+=++_)^_++,
substr(__, ___ = length(__)-_--),
"-",_,
index("=ANEBARPRAYUNULUGEPCTOVEC",
substr(__, ___-= _,_) ) /_,
"-",_, substr(__, ___-= _+!!_),
" ",_, __, ___ = ":",_,
substr(__,_+_),___,_,!_)
}
BEGIN {
print "yyyy-mm-dd hh-mn-sc" (OFS = ",\t") "val"
}
($++NF = ____($!_))^_'
yyyy-mm-dd hh-mn-sc, val
00:00H01JUN2021, 1.900, 2021-06-01 00:00:00
00:10H01JUN2021, 2.400, 2021-06-01 00:10:00
00:20H01JUN2021, 2.100, 2021-06-01 00:20:00
00:30H01JUN2021, 2.300, 2021-06-01 00:30:00
00:40H01JUN2021, 2.00, 2021-06-01 00:40:00
00:50H01JUN2021, 2.300, 2021-06-01 00:50:00
01:00H01JUN2021, 2.300, 2021-06-01 01:00:00
01:10H01JUN2021, 0.000, 2021-06-01 01:10:00
01:20H01JUN2021, 2.200, 2021-06-01 01:20:00
01:30H01JUN2021, 0.100, 2021-06-01 01:30:00
===================
To map english month names (full or abbr.) of any casing to month #, this extremely-odd-looking lookup string suffices —
it pre-segregates the input by whether 2nd letter is A|a - i.e. Jan / March / May
then performs reference string position lookup of the 3rd letter
function month_name_to_num(__,_) {
return \
index(substr("n_r_yb_r_nlgptvc",
((_+=++_)-+-++_)^(__!~"^.[Aa]")),
tolower(substr(__,_--,--_) ) )
}
OCT 10
AUGUST 8
March 3
May 5
October 10
November 11
February 2
JUNE 6
NOV 11
JUL 7
December 12
OCTOBER 10
FEBRUARY 2
JANUARY 1
MARCH 3
APRIL 4
June 6
April 4
September 9
NOVEMBER 11
January 1
FEB 2
MAY 5
DEC 12
MAY 5
JAN 1
JULY 7
SEP 9
August 8
SEPTEMBER 9
July 7
DECEMBER 12
MAR 3
APR 4
JUN 6
AUG 8
if you don't want to use regex, this function variant bypasses the need to allocate extra temp variable(s) by repurposing the data input one(s) —- something uniquely convenient in weakly-typed languages like awk :
function monthname2num(_) {
return \
index("=anebarprayunulugepctovec",
tolower(substr(_ "",_+=_^=_,_)))/_
}

How to cut the first Sunday to Saturday of each month in a year?

We have green zone logic where the job has to run only between first Sunday to Saturday, i.e. 7 days starting from first Sunday of every month. I'm using the below awk command to get that, but somewhere it is breaking. I'm just trying for first 3 months i.e Jan to March
seq 75 | awk ' BEGIN {ti=" 0 0 0"}
function dtf(fmt,dy) { return strftime(fmt,mktime("2020 1 " dy ti)) }
{ day=dtf("%A %F",$0);mm=dtf("%m",$0);if(day~/Sunday/ || a[mm]) a[mm]++ ; if(a[mm]<8) print day } '
My output is below, which is incorrect:
Wednesday 2020-01-01
Thursday 2020-01-02
Friday 2020-01-03
Saturday 2020-01-04
Sunday 2020-01-05
Monday 2020-01-06
Tuesday 2020-01-07
Wednesday 2020-01-08
Thursday 2020-01-09
Friday 2020-01-10
Saturday 2020-01-11
Saturday 2020-02-01
Sunday 2020-02-02
Monday 2020-02-03
Tuesday 2020-02-04
Wednesday 2020-02-05
Thursday 2020-02-06
Friday 2020-02-07
Saturday 2020-02-08
Sunday 2020-03-01
Monday 2020-03-02
Tuesday 2020-03-03
Wednesday 2020-03-04
Thursday 2020-03-05
Friday 2020-03-06
Saturday 2020-03-07
Expected output:
Sunday 2020-01-05
Monday 2020-01-06
Tuesday 2020-01-07
Wednesday 2020-01-08
Thursday 2020-01-09
Friday 2020-01-10
Saturday 2020-01-11
Sunday 2020-02-02
Monday 2020-02-03
Tuesday 2020-02-04
Wednesday 2020-02-05
Thursday 2020-02-06
Friday 2020-02-07
Saturday 2020-02-08
Sunday 2020-03-01
Monday 2020-03-02
Tuesday 2020-03-03
Wednesday 2020-03-04
Thursday 2020-03-05
Friday 2020-03-06
Saturday 2020-03-07
How can I adjust the awk command to get the expected output?
Any other solutions using other bash tools are also welcome.
I suggest the following alternative to awk:
#! /usr/bin/env bash
for month in {01..03}; do
for day in {01..13}; do
date -d "2020-$month-$day" '+%A %F'
done |
grep -A6 -m1 -F Sunday
done
The script is not very efficient, but does the job. For each month, we simply print the dates of the 13 first days in that month. We know that the green zone has to be in that area, therefore we do not need the remaining days of the month.
The date format is Weekday YYYY-MM-DD. We use grep to find and print the first Sunday, print the 6 days behind that Sunday (-A6) and exit because we limited the search to one match (-m1).
The procedure described above is done for each of the months 1 to 3.
Here's a simple way to get GNU awk to create a list of dates and day names for any given year:
$ cat tst.awk
BEGIN {
year = (year == "" ? 2020 : year)
beg = mktime(year " 1 1 12 0 0")
for (i=0; i<=400; i++) {
dateday = strftime("%F %A", beg+24*60*60*i)
split(dateday,d,/[ -]/)
if ( d[1] != year ) {
break
}
print d[1], d[2], d[3], d[4]
}
}
.
$ awk -f tst.awk | head -20
2020 01 01 Wednesday
2020 01 02 Thursday
2020 01 03 Friday
2020 01 04 Saturday
2020 01 05 Sunday
2020 01 06 Monday
2020 01 07 Tuesday
2020 01 08 Wednesday
2020 01 09 Thursday
2020 01 10 Friday
2020 01 11 Saturday
2020 01 12 Sunday
2020 01 13 Monday
2020 01 14 Tuesday
2020 01 15 Wednesday
2020 01 16 Thursday
2020 01 17 Friday
2020 01 18 Saturday
2020 01 19 Sunday
2020 01 20 Monday
I'm starting at noon and looping from 0 to 400 days and breaking when the year changes just so I don't have to try to accommodate DST or leap years or leap seconds in the determination of days in the year in a more accurate calculation.
Just add some code to test for the current month being different from the previous and the current day name being a Sunday and print 7 days starting there, e.g.:
$ cat tst.awk
BEGIN {
year = (year == "" ? 2020 : year)
beg = mktime(year " 1 1 12 0 0")
for (i=0; i<=400; i++) {
dateday = strftime("%F %A", beg+24*60*60*i)
split(dateday,d,/[ -]/)
if ( d[1] != year ) {
break
}
dayName[d[2]+0][d[3]+0] = d[4]
}
for (monthNr=1; monthNr<=3; monthNr++) {
for (dayNr=1; dayNr in dayName[monthNr]; dayNr++) {
if (dayName[monthNr][dayNr] == "Sunday") {
for (i=0; i<7; i++) {
printf "%s %04d-%02d-%02d\n", dayName[monthNr][dayNr+i], year, monthNr, dayNr+i
}
break
}
}
}
}
.
$ awk -f tst.awk
Sunday 2020-01-05
Monday 2020-01-06
Tuesday 2020-01-07
Wednesday 2020-01-08
Thursday 2020-01-09
Friday 2020-01-10
Saturday 2020-01-11
Sunday 2020-02-02
Monday 2020-02-03
Tuesday 2020-02-04
Wednesday 2020-02-05
Thursday 2020-02-06
Friday 2020-02-07
Saturday 2020-02-08
Sunday 2020-03-01
Monday 2020-03-02
Tuesday 2020-03-03
Wednesday 2020-03-04
Thursday 2020-03-05
Friday 2020-03-06
Saturday 2020-03-07
There are slightly more efficient ways to do it but the above is clear and simple and will run in the blink of an eye.
A (rather wordy - I don't have time to make it shorter:-) ) Perl solution:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Time::Piece;
use Time::Seconds;
my $year = shift || localtime->year;
first_week($year, $_) for 1 ..12;
sub first_week {
my ($yr, $mn) = #_;
$mn = sprintf '%02d', $mn;
# Use midday to avoid DST issues
my $start = Time::Piece->strptime(
"$year-$mn-01 12:00:00",
'%Y-%m-%d %H:%M:%S'
);
$start += ONE_DAY while $start->day ne 'Sun';
for (1 .. 7) {
say $start->strftime('%A %Y-%m-%d');
$start += ONE_DAY;
}
}
Try this
for i in $(seq 12); do cal ${i} 2020 | awk -v month=${i} 'NF==7 && !/^Su/{ for (j=0;j<=6;j++){print "2020-"month"-"$1+j;}exit}'
EDIT : Updated code for printing day
for i in $(seq 2); do cal ${i} 2020 | awk -v month=${i} 'NF==7 && !/^Su/{for (j=0;j<=6;j++){print strftime("%A %F", mktime("2020 " month " " $1+j " 0 0 0"))}exit}'; done;
Demo for Jan and Feb
$for i in $(seq 2); do cal ${i} 2020 | awk -v month=${i} 'NF==7 && !/^Su/{a[0]="Sunday";a[1]="Monday";a[2]="Tuesday";a[3]="Wednesday";a[4]="Thursday";a[5]="Friday";a[6]="Saturday";for (j=0;j<=6;j++){print a[j]" " "2020-"month"-"$1+j}exit}'; done;
Sunday 2020-1-5
Monday 2020-1-6
Tuesday 2020-1-7
Wednesday 2020-1-8
Thursday 2020-1-9
Friday 2020-1-10
Saturday 2020-1-11
Sunday 2020-2-2
Monday 2020-2-3
Tuesday 2020-2-4
Wednesday 2020-2-5
Thursday 2020-2-6
Friday 2020-2-7
Saturday 2020-2-8
$
With Perl, using DateTime
use warnings;
use strict;
use feature 'say';
use DateTime;
my $dt = DateTime->new(year => 2020, month => 1, day => 1);
my $first_sunday = 7 - $dt->day_of_week + 1; # day of month for first Sun
while (1) {
my $day = $dt->day;
if ($day >= $first_sunday and $day < $first_sunday + 7) {
say $dt->ymd, " (", $dt->day_abbr, ")";
}
}
continue {
$dt->add(days => 1);
if ($dt->day == 1) { # new month
last if $dt->month > 3;
$first_sunday = 7 - $dt->day_of_week + 1;
}
}
This keeps a state (on the first in a month in finds out what day the first Sunday is), what is quite suitable if the program is meant to generate and go through all dates from the span of interest.
On the other hand, the program may need to check for a given day; perhaps it runs daily and needs to check for that day. Then it is simpler to see whether the day is between the first and second Sunday in the month
my $dt = DateTime->today;
while ( $dt->add(days => 1)->month <= 3) {
if ($dt->day_of_week == 7) { # it's a Sunday
if ($dt->weekday_of_month == 1) { # first Sunday in the month
say $dt->ymd, " (", $dt->day_abbr, ")";
}
}
else {
my $sdt = $dt->clone; # preserve $dt
$sdt->subtract( $dt->day_of_week ); # drop to previous Sunday
if ($sdt->weekday_of_month == 1) { # was first Sunday in the month
say $dt->ymd, " (", $dt->day_abbr, ")";
}
}
}
The while loop around the code is there to facilitate a check.
For days other than Sunday we drop to the past Sunday, to check whether that was the first Sunday in the month. If so, then our day is within the required interval. If the day is a Sunday we only need to check whether it is the first one in the month.
The code can be made a bit more efficient and concise if that matters
if ( (my $dow = $dt->day_of_week) == 7) {
if ($dt->weekday_of_month == 1) {
say $dt->ymd, " (", $dt->day_abbr, ")";
}
}
elsif ( $dt->clone->subtract(days => $dow)->weekday_of_month == 1 ) {
say $dt->ymd, " (", $dt->day_abbr, ")";
}
... on the account of readability.
$ printf "%s\n" 2020-{01..03}-01 \
| xargs -I{} date -d "{}" "+{} %u" \
| join -j3 - <(seq 0 6) \
| xargs -n3 sh -c 'date -d "$1 + 7 days - $2 days + $3 days" "+%A %F"' --
There is some nasty stuff in here, but I'll try to explain. The idea is to compute the day of the week of the first day of the month (assume u). If you know that, you know directly which day is the first Sunday (7-u days later). So from that point forward you only need to compute the next 6 days.
Use brace expansion to generate the months you are interested in
Use xargs to compute the day of the week and output it as YYYY-MM-DD u
Per day, we want to create a list of 7 strings YYYY-MM-DD u d where d runs from 0 to 6. For this we use a nasty join hack. By telling join to join to files on a non-existing field, we create an outer product.
Use xargs in combination with sh to create a command that accepts 3 arguments and do the computation.
This method is now easily expanded to other months and years:
$ printf "%s\n" 20{20..30}-{01..12}-01 | xargs ...
The above looks a bit messy, and you might be more interested in the loop version:
for yyyymm in {2020..2030}-{01..03}; do
u=$(date -d "$yyyymm-01" "+%u");
for ((dd=7-u;dd<14-u;++dd)); do
date -d "$yyyymm-01 + $dd days" "+%A %F"
done
done
Previous solution:
This is for the first 3 months of 2020:
$ printf "%s\n" 2020-{01..03}-{01..13} \
| xargs -n1 -I{} date -d '{}' '+%A %F' \
| awk -F"[- ]" '/Sun/{a[$3]++} a[$3]==1'
This is for the first years 2020 till 2030
$ printf "%s\n" 20{20..30}-{01..12}-{01..13} \
| xargs -n1 -I{} date -d '{}' '+%A %F' \
| awk -F"[- ]" '/Sun/{a[$2,$3]++} a[$2,$3]==1'
This is understood in 3 steps:
Use brace-expansion to create a list of the first 13 days of months and years you are interested in. This works nicely because the bash starts expanding left to right. This means that the day is the fast-running index. We ask for the first 13 days, because we know that the first Sunday must be within the first 7 days.
Convert the days to the expected format using xargs and date
Use awk to do the filtering.
By adding one more condition, I'm able to make it work. a[mm]<8 && a[mm]>0
seq 75 | awk '
BEGIN { ti=" 0 0 0" }
function dtf(fmt,dy) {
return strftime(fmt,mktime("2020 1 " dy ti))
}
{ day=dtf("%A %F",$0);
mm=dtf("%m",$0);
if(day~/Sunday/ || a[mm]) a[mm]++ ;
if(a[mm]<8 && a[mm]>0 ) print day
}'
Output:
Sunday 2020-01-05
Monday 2020-01-06
Tuesday 2020-01-07
Wednesday 2020-01-08
Thursday 2020-01-09
Friday 2020-01-10
Saturday 2020-01-11
Sunday 2020-02-02
Monday 2020-02-03
Tuesday 2020-02-04
Wednesday 2020-02-05
Thursday 2020-02-06
Friday 2020-02-07
Saturday 2020-02-08
Sunday 2020-03-01
Monday 2020-03-02
Tuesday 2020-03-03
Wednesday 2020-03-04
Thursday 2020-03-05
Friday 2020-03-06
Saturday 2020-03-07
As a additional note, though I hardcoded 1 for the month, when the day parameter is >31 mktime() just moves to the next month. So in a way you can pass julian day to mktime with month set to 1.
echo -e "1\n31\n32\n60\n61\n366" | awk '
BEGIN { ti=" 0 0 0" }
function dtf(fmt,dy) {
return strftime(fmt,mktime("2020 1 " dy ti))
}
{
day=dtf("%A %F",$0);
j=dtf("%j",$0);
print j,day
}'
Output:
001 Wednesday 2020-01-01
031 Friday 2020-01-31
032 Saturday 2020-02-01
060 Saturday 2020-02-29
061 Sunday 2020-03-01
366 Thursday 2020-12-31

how to calculate total elapsed time

how to calculate elapsed time based on
start time=
[user001a#dev51 logs]# grep 'Recovery Manager'
refresh_03Jun2019_0250.log|head -1|awk -F'on ' '{print $NF}';
Jun 3 02:50:02 2019
[user001a#dev51 logs]#
end time=
[user001a#dev51 logs]# ls -l refresh_03Jun2019_0250.log
-rw-r--r--. 1 user001a grp001a 170050 Jun 3 05:06
refresh_03Jun2019_0250.log
[user001a#dev51 logs]#
Note - stat is missing birth time so stat might not be a good option time calculate file create and modify time:
[user001a#dev51 logs]# stat refresh_03Jun2019_0250.log
File: `refresh_03Jun2019_0250.log'
Size: 170050 Blocks: 344 IO Block: 4096 regular file
Device: 811h/2065d Inode: 1474545 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 219/ user001a) Gid: ( 219/grp001a)
Access: 2019-06-03 05:06:40.830829026 -0400
Modify: 2019-06-03 05:06:40.827828883 -0400
Change: 2019-06-03 05:06:40.827828883 -0400
[user001a#dev51 logs]#
Sample1 output:
StartTime=June 3, 2019 at 2:50:02 am
EndTime=June 3, 2019 at 5:06:40 am
ElapsedTime=2 hours, 16 minutes and 38 seconds
Sample2 output:
ElapsedTime=2 hours, 16 minutes and 38 seconds
Limitation of this solution: Max 23 hours. For more, days need to be added.
StartTime="June 3, 2019 at 2:50:02 am"
EndTime="June 3, 2019 at 5:06:40 am"
StartTimeInEpoch=`echo $StartTime | sed 's/at //g' | date -f- +"%s"`
EndTimeInEpoch=`echo $EndTime | sed 's/at //g' | date -f- +"%s"`
echo $EndTimeInEpoch-$StartTimeInEpoch | bc | sed 's/^/#/g' | date -u -f- "+%_H hours %_M minutes %_S seconds"
Output:
2 hours 16 minutes 38 seconds
Assuming you've got your dates in variables StartTime and EndTime. It's necessary to remove at from them, sed do this. Then both dates are converted to epoch time +"%s" do the trick. -f- tells date to take date from stdin (pipe). Then we can subtract the dates, add # to the beginning and format with date. -u mean UTC time - no time shift.

Find Date and Time clashes

I'm having a hard time identifying booking clashes in my system. I've developed a booking system that integrates with our core system. The column data that is stored in my booking system is:
ResourceID, StartDate, EndDate, StartTime, EndTime
I need to ensure that when another user tries to book a resource, the resource is available. This question has been asked by many, but its usually with one date and start and end time or intersecting between a start date and enddate .
If I wanted to solely check on intersecting dates or time, the formula to use would be
a=existing_booking
b=new_booking
overlap = a.start < b.end && b.start < a.end;
I then found a really good and interesting post regarding overlapping resources containing both start time and end time Code Logic to prevent clash between two reservations but they deal with a reoccurring event over multiple days.
I need to ensure if a resource is booked between 8:50 to 22:00 from the 07-23-2016 to 07-29-2016 ; that someone trying to book on the 07-25-2016 to 07-25-2016 at 08:00 to 08:30 can't book it as it is booked out. After numerous searches I can't seem to find the formula for this, can anyone help post the algorithm or send me a link to an existing answer as its driving me nuts.
could you try the below logic?
01 if (new.StartDate > existing.EndDate OR new.EndDate < existing.StartDate) {
02 resource is available for booking
03 } else if (new.EndDate = existing.StartDate AND new.StartDate <= existing.StartDate) {
04 if (new.EndTime =< existing.StartTime OR new.StartTime >= existing.EndTime) {
05 resource is available for booking
06 } else {
07 resource is NOT available for booking
08 }
09 } else if (new.StartDate = existing.EndDate) {
10 if (new.StartTime >= existing.EndTime) {
11 resource is available for booking
12 } else {
13 resource is NOT available for booking
14 }
15 } else {
16 resource is NOT available for booking
17 }
lets check on a case to case basis;
pass - resource is available
fail - resource is NOT available
An existing booking is in place
ResourceID | StartDate | EndDate | StartTime | EndTime
ID1234 | 07-23-2016 | 07-29-2016 | 08:50 | 22:00
Case 1
ResourceID | StartDate | EndDate | StartTime | EndTime
ID1234 | 07-25-2016 | 07-25-2016 | 08:00 | 08:30
result - this case would fail; #line 16 in the code above
Case 2
ResourceID | StartDate | EndDate | StartTime | EndTime
ID1234 | 07-23-2016 | 07-23-2016 | 08:00 | 08:30
result - this case would pass; #line 05 in the code above
Case 3
ResourceID | StartDate | EndDate | StartTime | EndTime
ID1234 | 07-29-2016 | 07-29-2016 | 22:01 | 22:30
result - this case would pass; #line 11 in the code above
Case 4
ResourceID | StartDate | EndDate | StartTime | EndTime
ID1234 | 07-23-2016 | 07-23-2016 | 09:00 | 11:30
result - this case would fail; #line 07 in the code above
Please do check and let me know if for any case the above logic would be giving an in correct answer.

datamapper, how do you filter a date field by month and year?

using datamapper, how do you filter a date field by month and year?
I can filter all records with today's date.
:date => Date.today
but i am curious if there is an elegant solution
for returning all records from december 2012.
posts.all
+----+------------+
| id | date |
+----+------------+
| 1 | 2009-10-20 |
| 2 | 2012-11-18 |
| 3 | 2012-12-10 |
| 4 | 2012-12-14 |
+----+------------+
posts.all(:date => Date.today)
+----+------------+
| id | date |
+----+------------+
| 4 | 2012-12-14 |
+----+------------+
I think your best bet is to use the date range form (which will result in an SQL "BETWEEN" query):
posts.all(:date => (Date.parse('2012-12-01') .. Date.parse('2012-12-31')))
For other years/months you'll have to lookup or compute the last date of the month instead of hardcoding them as in the example above.
def month_range(year, month)
d0 = Date.parse([year, month, 1].join('-'))
(d0 .. (d0>>1)-1)
end
posts.all(:date => month_range(2012, 12))

Resources