unix DATE command converts wrong in specific years - linux

The command
mydate=$(date -d "90 days 19850101" +%Y%m%d%H%M%S)
yields 19850401000000. But:
mydate=$(date -d "90 days 19830101" +%Y%m%d%H%M%S)
yields 19830401010000.
How is it possible that in year 1983 one hour is added on 1 April (which is a result I don't want), while for the year 1985 the answer is correct?

Related

Get Tuesday and Wednesday next week, with Linux date command

At a Linux shell, you can do something like:
date -d "next Tuesday"
To get next Tuesday.
My issue is this:
I want to get Tuesday of NEXT WEEK. So if I'm currently on Monday, I want it to go 7 days forward to next week, then evaluate "next Tuesday". Is there a way to chain the date evaluations somehow?
To further elaborate, if I am on a Wednesday, then next week's Tuesday is just 6 days away
date is cleverer than you'd think
~: date -d "next tuesday"
Tue Feb 2 00:00:00 GMT 2016
~: date -d "1 week next tuesday"
Tue Feb 9 00:00:00 GMT 2016
~:
If you want to get the Tuesday of next week you can find the start of next week, then add a day
~: date -d "1 day next monday"
Tue Feb 2 00:00:00 GMT 2016
If you want it to be slightly clear you can use
~: date -d "next Monday + 1 day"
Tue Feb 2 00:00:00 GMT 2016
Based on Charles Duffy's comments it might be worth noting on my machine
~: date --version #on RHEL6
date (GNU coreutils) 8.4
<license stuff (GPLv3)>
The only way to do this reliably is to first get the next "beginning of week day" (which might vary from region to region; for this I'll assume it's Sunday), then request a day 0-6 days in the future, where 0 through 6 stand in for Sunday through Saturday, respectively.
$ bow=$(date -d "next Sunday")
$ date -d "$bow + 0 days"

Set a cron every 10 days starting from 16th January

How to set a cron to execute every 10 days starting from 16th January? Would this suffice?
30 7 16-15/10 * * command >/dev/null
The above starts at 7.30 AM, 16th of every month and ends on next month 15th and repeats every 10 days. I don't think what I have above is correct. Can anyone tell me how to set up the cron so that month ends are taken into account and every 10 days the command is executed starting from 16th January this year 2016?.
As William suggested, cron can't handle this complexity by itself. However, you can run a cron job more frequently, and use something else for the logic. For example;
30 7 16-31 1 * date '+\%j' | grep -q '0$' && yourcommand
30 7 * 2-12 * date '+\%j' | grep -q '0$' && yourcommand
This date format string prints the day of the year, from 001 to 365. The grep -q will do a pattern match, NOT print the results, but return a success of a failure on the basis of what it finds. Every 10 days, the day of the year ends in a zero. On those days, yourcommand gets run.
This has a problem with the year roll-over. A more complex alternative might be to do a similar grep on a product of date '+%s' (the epoch second), but you'll need to do math to turn seconds into days for analysis by grep. This might work (you should test):
SHELL=/bin/bash
30 7 * * * echo $(( $(date '+%s') / 86400 )) | grep '0$' && yourcommand
(Add your Jan 16th logic too, of course.)
This relies on the fact that shell arithmetic can only handle integers. The shell simply truncates rather than rounding.
UPDATE
In a comment on another answer, you clarified your requirements:
The command should start executing on January 16th, and continue like on January 26th, February 5th, February 15th and so on – jai
For this, the epoch-second approach is probably the right direction.
% date -v1m -v16d -v7H -v30M -v0S '+%s'
1452947400
(I'm in FreeBSD, hence these arguments to date.)
SHELL=/bin/bash
30 7 * * * [[ $(( ($(date '+\%s') - 1452947400) \% 864000 )) == 0 ]] && yourcommand
This expression subtracts the epoch second of 7:30AM Jan 16 (my timezone) from the current time, and tests whether the resultant difference is divisible by 10 days. If it is, the expression evaluates true and yourcommand is run. Note that $(( 0 % $x )) evaluates to 0 for any value of $x.
This may be prone to error if cron is particularly busy and can't get to your job in the one second where the math works out.
If you want to make this any more complex (and perhaps even if it's this complex), I recommend you move the logic into a separate shell script to handle the date comparison math. Especially if you plan to add a fudge factor to allow for jobs to miss their 1-second window .. that would likely be multiple lines of script, which is awkward to maintain in a single cronjob entry.
Observation: the math capabilities of cron are next to non-existent. The math capabilities of the Unix tools are endless.
Conclusion: move the problem from the cron domain to the shell domain.
Solution: run this each day with 30 7 * * * /path/to/script in the crontab:
#!/bin/sh
PATH=$(/usr/bin/getconf PATH)
if test $(($(date +%j) % 10)) = 6; then
your_command
fi
This tests whether the day-of-year modulo 10 is 6, like it is for January 16 (and January 6th is already in the past...).
Thinking outside the box:
Fix your requirement. Convince whoever came up with that funny 10 day cycle to accept a 7 day cycle. So much easier for cron. This is following the KISS principle.
0 30 7 1/10 * ? * command >/dev/null
Output for the above express is,
Saturday, January 16, 2016 7:30 AM
1. Thursday, January 21, 2016 7:30 AM
2. Sunday, January 31, 2016 7:30 AM
3. Monday, February 1, 2016 7:30 AM
4. Thursday, February 11, 2016 7:30 AM
5. Sunday, February 21, 2016 7:30 AM
Output for your expression
i.e 30 7 16-15/10 * * command >/dev/null
2016-01-15 07:30:00
2016-02-15 07:30:00
2016-03-15 07:30:00
2016-04-15 07:30:00
2016-05-15 07:30:00
2016-06-15 07:30:00
2016-07-15 07:30:00
2016-08-15 07:30:00
2016-09-15 07:30:00
2016-10-15 07:30:00
The closest syntax would like this:
30 7 1-30/10 * *
30 7 1-31/10 * *
30 7 1-28/10 * *
30 7 1-29/10 * *
You can test the cron expression here http://cron.schlitt.info/

How can I use date command in Linux to read ANSI date

ANSI date numbers starts from January 1st 1601 (day 1).
So how to get the following to work in a bash command in Linux?
I want:
# ANSI / UNIX epoch delta is 134774 days
$ date -ud ‘1601 -01 -01 + 134774 days ’ +%F
1970 -01 -01
But I get
date: invalid date '1601-01-01+134774 days'
To answer my own question as I meanwhile found this site: https://unix.stackexchange.com/questions/7688/date-years-prior-to-1901-are-treated-as-invalid
It's because I'm on a 32-bit machine.
date -ud '1901-12-14 + 24855 days' +%F
will give
1970-01-01

Bash date command invalid date

In linux bash when I enter date -d "1986-01-01" it shows error
date: invalid date "1986-01-01"
when date -d "1986-01-02" it works
when date -d "1987-01-01" it also works
Why date -d "1986-01-01" shows error in Linux Bash shell.
I am using Fedora 16
Nepal changed its timezone at the beginning of 1986. The following table is copied from the tzdata package:
# Zone NAME GMTOFF RULES FORMAT [UNTIL]
Zone Asia/Kathmandu 5:41:16 - LMT 1920
5:30 - IST 1986
5:45 - NPT # Nepal Time
That means that on Jan 1 1986 the time from 00:00:00 to 00:14:59 is not valid. The following two commands show, that the first day of 1986 started with 00:15:00:
$ TZ=Asia/Kathmandu date -d '1985-12-31 23:59:59' '+%s'
504901799
$ TZ=Asia/Kathmandu date -d '1986-01-01 00:15:00' '+%s'
504901800
So the error message of date is correct. The date is invalid in this timezone. I am not sure what you are doing with the result of this command. However, you can try to use UTC because all dates are valid and unambiguous in UTC:
$ TZ=UTC date -d '1986-01-01'
Wed Jan 1 00:00:00 UTC 1986
I think you are using alphabet "O" in upper case instead of number "0" in the command :)

Calendar calculations in bash

I want to do some calendar manipulations in bash - specifically, I want to figure out the last date of a given month (including leap-year, and a preparing a table for a lookup is not a valid solution for me).
Supposedly I have the following code:
$year=2009
$start_month=2
$end_month=10
for $month in $(seq $start_month $end_month); do
echo "Last date of "$(date +"%B" -d "${year}-${month}-01")" is: " ???
done
I can't figure out how to do something like this. I though date -d would work like POSIX mktime and fold invalid dates to their valid equivalents, so I could say something like date -d "2009-03-00" and get '2009-02-28', but no such luck.
Is there anyway to do it using only what is available on bash in a standard GNU environment?
Try: date -d 'yesterday 2009-03-01'
Intuitive I know. Earlier versions of date used to work the POSIX way.
date(1)'s -d is GNU specific; so using that will only work on GNU Linux.
A more portable solution (this should even work in sh AFAIK), is this:
: $(cal 4 2009); echo $_
If you don't mind playing with grep/awk/perl, you can take a look at cal.
$ cal 4 2009
April 2009
Su Mo Tu We Th Fr Sa
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
Edit (MarkusQ): To atone for my joke solution below I'll contribute to yours:
cal 4 2009 | tr ' ' '\n' | grep -v ^$ | tail -n 1
Well, one way would be to watch the current date in a loop until the month component changes, saving the day component for one round. That would give you both the first and last day of the month, but it might be too slow.
Posted 1 April 2009

Resources