Rocksolid Light

Welcome to Rocksolid Light

mail  files  register  newsreader  groups  login

Message-ID:  

Whom computers would destroy, they must first drive mad.


devel / comp.unix.shell / Re: Datediff script

SubjectAuthor
* Datediff scriptcastAway
+* Datediff scriptJanis Papanagnou
|+* Datediff scriptcastAway
||+* Datediff scriptSpiros Bousbouras
|||`- Datediff scriptcastAway
||`* Datediff scriptJanis Papanagnou
|| +* Datediff scriptJanis Papanagnou
|| |`* Datediff scriptJanis Papanagnou
|| | `* Datediff scriptcastAway
|| |  `- Datediff scriptKeith Thompson
|| +* Datediff scriptcastAway
|| |`* Datediff scriptJanis Papanagnou
|| | `* Datediff scriptChris Elvidge
|| |  +- Datediff scriptSpiros Bousbouras
|| |  `- Datediff scriptcastAway
|| +* Datediff scriptcastAway
|| |`* Datediff scriptcastAway
|| | `* Datediff scriptJanis Papanagnou
|| |  `- Datediff scriptcastAway
|| +* Datediff scriptcastAway
|| |+* Datediff scriptBen Bacarisse
|| ||`* Datediff scriptcastAway
|| || `- Datediff scriptLew Pitcher
|| |+- Datediff scriptJanis Papanagnou
|| |`- Datediff scriptJohn-Paul Stewart
|| `* Datediff scriptcastAway
||  `- Datediff scriptJanis Papanagnou
|`* Datediff scriptcastAway
| +- Datediff scriptJanis Papanagnou
| `* Datediff scriptJanis Papanagnou
|  `* Datediff scriptJanis Papanagnou
|   `- Datediff scriptJanis Papanagnou
`* Datediff scriptcastAway
 +* Datediff scriptJanis Papanagnou
 |+* Datediff scriptcastAway
 ||+- Datediff scriptcastAway
 ||`- Datediff scriptcastAway
 |`* Datediff scriptcastAway
 | `- Datediff scriptcastAway
 `* Datediff scriptJanis Papanagnou
  `* Datediff scriptcastAway
   `- Datediff scriptJanis Papanagnou

Pages:12
Re: Datediff script

<tl46mq$2fvu5$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6847&group=comp.unix.shell#6847

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: janis_papanagnou+ng@hotmail.com (Janis Papanagnou)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Thu, 17 Nov 2022 03:36:09 +0100
Organization: A noiseless patient Spider
Lines: 18
Message-ID: <tl46mq$2fvu5$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tkk621$l9ma$1@dont-email.me>
<tkoep2$1b7q$1@gioia.aioe.org>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
Injection-Date: Thu, 17 Nov 2022 02:36:10 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="e227ec2413eb6bef2b12cafeaf56588d";
logging-data="2621381"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX18KYRwg0V/xIoJ+zibhs6Yj"
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101
Thunderbird/45.8.0
Cancel-Lock: sha1:M2pXXRWN+h5IL3IZ2uL5UJXITUE=
In-Reply-To: <tkoep2$1b7q$1@gioia.aioe.org>
 by: Janis Papanagnou - Thu, 17 Nov 2022 02:36 UTC

On 12.11.2022 16:40, castAway wrote:
>
> However, the functionality seems to be very basic:
>
> % $AST/date -E '2002-01-01' '2012-01-01'
> 9Y11M
> % $AST/date -E '12:01:01' '19:02:02'
> 7h01m
> % $AST/date -E '2002-01-01 12:01:01' '2012-01-01 19:01:01'
> 9Y11M

What does it return if you provide ISO dates?

$AST/date -E '2002-01-01T12:01:01' '2012-01-01T19:01:01'

Janis

Re: Datediff script

<tl48o7$2iue4$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6848&group=comp.unix.shell#6848

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: janis_papanagnou+ng@hotmail.com (Janis Papanagnou)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Thu, 17 Nov 2022 04:11:02 +0100
Organization: A noiseless patient Spider
Lines: 23
Message-ID: <tl48o7$2iue4$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tkk621$l9ma$1@dont-email.me>
<tkoep2$1b7q$1@gioia.aioe.org> <tl46mq$2fvu5$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
Injection-Date: Thu, 17 Nov 2022 03:11:03 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="e227ec2413eb6bef2b12cafeaf56588d";
logging-data="2718148"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX19KjZ9VlXKouV6VYKn0f740"
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101
Thunderbird/45.8.0
Cancel-Lock: sha1:XtX1efpJDgpR/PzW5s3eevyPdOk=
In-Reply-To: <tl46mq$2fvu5$1@dont-email.me>
X-Enigmail-Draft-Status: N1110
 by: Janis Papanagnou - Thu, 17 Nov 2022 03:11 UTC

On 17.11.2022 03:36, Janis Papanagnou wrote:
> On 12.11.2022 16:40, castAway wrote:
>>
>> However, the functionality seems to be very basic:
>>
>> % $AST/date -E '2002-01-01' '2012-01-01'
>> 9Y11M
>> % $AST/date -E '12:01:01' '19:02:02'
>> 7h01m
>> % $AST/date -E '2002-01-01 12:01:01' '2012-01-01 19:01:01'
>> 9Y11M
>
> What does it return if you provide ISO dates?
>
> $AST/date -E '2002-01-01T12:01:01' '2012-01-01T19:01:01'

Nevermind. I found an AST date in some forgotten directory and the
result is the same. Seems we'd need two calls for sub-day accuracy,
and some formatting to create correctly formatted ISO time periods.

> Janis
>

Re: Datediff script

<tl497o$2ivbj$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6849&group=comp.unix.shell#6849

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: janis_papanagnou+ng@hotmail.com (Janis Papanagnou)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Thu, 17 Nov 2022 04:19:20 +0100
Organization: A noiseless patient Spider
Lines: 31
Message-ID: <tl497o$2ivbj$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tkk621$l9ma$1@dont-email.me>
<tkoep2$1b7q$1@gioia.aioe.org> <tl46mq$2fvu5$1@dont-email.me>
<tl48o7$2iue4$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
Injection-Date: Thu, 17 Nov 2022 03:19:20 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="e227ec2413eb6bef2b12cafeaf56588d";
logging-data="2719091"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1/VXnpi+dq/J5iJiIGplXUH"
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101
Thunderbird/45.8.0
Cancel-Lock: sha1:vkZ88zQXkY/xmILsbD+LN8bvYuw=
In-Reply-To: <tl48o7$2iue4$1@dont-email.me>
 by: Janis Papanagnou - Thu, 17 Nov 2022 03:19 UTC

On 17.11.2022 04:11, Janis Papanagnou wrote:
> On 17.11.2022 03:36, Janis Papanagnou wrote:
>> On 12.11.2022 16:40, castAway wrote:
>>>
>>> However, the functionality seems to be very basic:
>>>
>>> % $AST/date -E '2002-01-01' '2012-01-01'
>>> 9Y11M
>>> % $AST/date -E '12:01:01' '19:02:02'
>>> 7h01m
>>> % $AST/date -E '2002-01-01 12:01:01' '2012-01-01 19:01:01'
>>> 9Y11M
>>
>> What does it return if you provide ISO dates?
>>
>> $AST/date -E '2002-01-01T12:01:01' '2012-01-01T19:01:01'
>
> Nevermind. I found an AST date in some forgotten directory and the
> result is the same. Seems we'd need two calls for sub-day accuracy,
> and some formatting to create correctly formatted ISO time periods.

And the man page (strelapsed.3) says:

The two largest time units are used, limiting the return value length
to at most 6 characters.

>
>> Janis
>>
>

Re: Datediff script

<tl6oea$8aj$1@gioia.aioe.org>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6850&group=comp.unix.shell#6850

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!aioe.org!svGMjj4JAseXRzIUqGFGng.user.46.165.242.75.POSTED!not-for-mail
From: no@where.com (castAway)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Thu, 17 Nov 2022 22:51:05 -0300
Organization: Aioe.org NNTP Server
Message-ID: <tl6oea$8aj$1@gioia.aioe.org>
References: <tkju7j$54v$1@gioia.aioe.org> <tkk621$l9ma$1@dont-email.me>
<tkkfse$1e9d$1@gioia.aioe.org> <tkltmu$sve7$1@dont-email.me>
<tkqqm4$vrv$1@gioia.aioe.org> <tkrjo7$asl$1@gioia.aioe.org>
<tl450e$2frv6$1@dont-email.me>
Mime-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Injection-Info: gioia.aioe.org; logging-data="8531"; posting-host="svGMjj4JAseXRzIUqGFGng.user.gioia.aioe.org"; mail-complaints-to="abuse@aioe.org";
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
Thunderbird/102.4.1
Content-Language: pt-BR, en-GB
X-Notice: Filtered by postfilter v. 0.9.2
 by: castAway - Fri, 18 Nov 2022 01:51 UTC

On 11/16/22 23:07, Janis Papanagnou wrote:
> No. The C source function is doing the last step as _binary_ operation
> ... & 7
> which is equivalent here to the _arithmetic_ counterpart
> ... % 8
> that uses the modulo operator (as opposed to bit-wise 'and').

Thanks, I had missed the operator was different! The explanation
could not be any clearer! I did some testing and could not find
differences in results using and not using the floor() function of Ksh.

Re: Datediff script

<tl9647$1an2$1@gioia.aioe.org>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6851&group=comp.unix.shell#6851

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!aioe.org!svGMjj4JAseXRzIUqGFGng.user.46.165.242.75.POSTED!not-for-mail
From: no@where.com (castAway)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Fri, 18 Nov 2022 20:56:54 -0300
Organization: Aioe.org NNTP Server
Message-ID: <tl9647$1an2$1@gioia.aioe.org>
References: <tkju7j$54v$1@gioia.aioe.org> <tkk621$l9ma$1@dont-email.me>
<tkkfse$1e9d$1@gioia.aioe.org> <tkltmu$sve7$1@dont-email.me>
<tl1dhb$25vcc$2@dont-email.me> <87k03uvet3.fsf@bsb.me.uk>
Mime-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Injection-Info: gioia.aioe.org; logging-data="43746"; posting-host="svGMjj4JAseXRzIUqGFGng.user.gioia.aioe.org"; mail-complaints-to="abuse@aioe.org";
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
Thunderbird/102.4.1
Content-Language: pt-BR, en-GB
X-Notice: Filtered by postfilter v. 0.9.2
 by: castAway - Fri, 18 Nov 2022 23:56 UTC

On 11/16/22 17:12, Ben Bacarisse wrote:
> castAway <no@where.com> writes:
>
>> On 11/11/22 13:36, Janis Papanagnou wrote:
>>
>>> function phase_of_the_moon (now) // 0-7, with 0: new, 4: full
>>
>> Bash integer arithmetics do floor rounding by deafults,
>
> No. Bash's integer division truncates towards zero. The manual says
> the results as "as in C", which presumably means modern C. In C90, x/y
> with either operand negative was implementation defined. Since C99 the
> result is truncated towards zero.
>

Thanks for the clarification! if the function was written in C code and
Bash is as in C, then they should behave similarly.

Re: Datediff script

<tl9e62$31r8a$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6852&group=comp.unix.shell#6852

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: lew.pitcher@digitalfreehold.ca (Lew Pitcher)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sat, 19 Nov 2022 02:14:26 -0000 (UTC)
Organization: A noiseless patient Spider
Lines: 32
Message-ID: <tl9e62$31r8a$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tkk621$l9ma$1@dont-email.me>
<tkkfse$1e9d$1@gioia.aioe.org> <tkltmu$sve7$1@dont-email.me>
<tl1dhb$25vcc$2@dont-email.me> <87k03uvet3.fsf@bsb.me.uk>
<tl9647$1an2$1@gioia.aioe.org>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Injection-Date: Sat, 19 Nov 2022 02:14:26 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="01e4d9303b2fc12a1457fd95bb77bd20";
logging-data="3206410"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX192O/RVfyIOykW5AIaP4R1ySB8wTFb2ZKY="
User-Agent: Pan/0.139 (Sexual Chocolate; GIT bf56508
git://git.gnome.org/pan2)
Cancel-Lock: sha1:FMbeW0zNyiN+4gsbF+s5TJjAiMY=
 by: Lew Pitcher - Sat, 19 Nov 2022 02:14 UTC

On Fri, 18 Nov 2022 20:56:54 -0300, castAway wrote:

> On 11/16/22 17:12, Ben Bacarisse wrote:
>> castAway <no@where.com> writes:
>>
>>> On 11/11/22 13:36, Janis Papanagnou wrote:
>>>
>>>> function phase_of_the_moon (now) // 0-7, with 0: new, 4: full
>>>
>>> Bash integer arithmetics do floor rounding by deafults,
>>
>> No. Bash's integer division truncates towards zero. The manual says
>> the results as "as in C", which presumably means modern C. In C90, x/y
>> with either operand negative was implementation defined. Since C99 the
>> result is truncated towards zero.
>>
>>
> Thanks for the clarification! if the function was written in C code and
> Bash is as in C, then they should behave similarly.

Not necessarily, because, even with the C language's inherent
limitations, you can still write C code to do almost anything.

For instance, the GNU Multiple Precision Arithmetic Library (which
provides an API to perform arbitrary precision arithmetic, operating on
signed integers, rational numbers, and floating-point numbers) is written
in C. And, even though the GMP library is "written in C code", it
certainly does NOT "behave similarly" to C language arithmetic.

--
Lew Pitcher
"In Skills, We Trust"

Re: Datediff script

<tlaivk$37mt8$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6853&group=comp.unix.shell#6853

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: no@where.com (castAway)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sat, 19 Nov 2022 09:42:28 -0300
Organization: A noiseless patient Spider
Lines: 1346
Message-ID: <tlaivk$37mt8$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Injection-Date: Sat, 19 Nov 2022 12:42:29 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="c8e09f965d0c49b79a6c694f0134c3d4";
logging-data="3398568"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1+jJkOXlQXaLdjOKVBdMV98yX/mOza3p+4="
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
Thunderbird/102.4.1
Cancel-Lock: sha1:PFmXI4RDgXAstlsPTTeA5XGNQDE=
In-Reply-To: <tkju7j$54v$1@gioia.aioe.org>
Content-Language: pt-BR, en-GB
 by: castAway - Sat, 19 Nov 2022 12:42 UTC

Hello!

I am afraid to have gone a little over the top. The lunar phase function
was implemented, as well as another function from NetHack to print following
Friday the 13th dates.

Porting the script to Ksh was OK. It was tested with AST Ksh93u+. I understand
one would generally omit Bc code because Ksh has got FP arithmetics already,
but it was left intact so it can run unders Bash. When the script code was
tested in my mobile phone Termux, Zsh emulation of Ksh didn't work because,
I believe, Zsh emulates Ksh88. It was not hard to setopt Zsh options and now
the code works with Zsh as well. The code looks a little clunky, in my
opinion, when it comes to all the hack code spread over about. Ksh93 `superior'
parameter scoping in functions was a litte hard to deal with but hopefully
the script code hacks will work correctly. Ksh code is much faster.

Most advice from c.u.shell was incorporated. There was a regression bug
in which UNIX times were not generated properly, that is now fixed. Improved
$TZ support (there were some out-of-range combinations of $TZ and date
offset which were unaccounted for). The script `calculation code' was
thoroughly tested in Ksh (see links for testing scripts in source). Sorry if
there are bugs still present. Specially, the new friday_13th() function
may still need some more work.
I will update it at my GitLab <https://gitlab.com/mountaineerbr/scripts>

I reckon it would be cool to have a shell function to convert UNIX times to
ISO-8601 (and RFC-5322) formats.

Hyroptatyr's C-code dateutils `datediff' is required to validate the script
calculations, that is run when the debug function is set.

Many thanks to all, it meant a whole lot for the code.

#!/usr/bin/env ksh
# datediff.sh - Calculate time ranges between dates
# v0.21 nov/2022 mountaineerbr GPLv3+
[[ $BASH_VERSION ]] && shopt -s extglob #bash2.05b+/ksh93u+/zsh5+
[[ $ZSH_VERSION ]] && setopt KSH_GLOB KSH_ARRAYS SH_WORD_SPLIT

HELP="NAME
${0##*/} - Calculate time ranges/intervals between dates


SYNOPSIS
${0##*/} [-NUM] [-Rrttuvvv] [-f\"FMT\"] \"DATE1\" \"DATE2\" [UNIT]
${0##*/} -FF [-vv] [[DAY_IN_WEEK] [DAY_IN_MONTH]] [START_DATE]
${0##*/} -e [-v] YEAR..
${0##*/} -l [-v] YEAR..
${0##*/} -m [-v] DATE..
${0##*/} -h


DESCRIPTION
Calculate time interval (elapsed) between DATE1 and DATE2 in var-
ious time units. The \`date' programme is optionally run to process
dates.

Other functions include checking if YEAR is leap, Easter date on
a given YEAR and phase of the moon at DATE.

In the main function, \`GNU date' accepts mostly free format human
readable date strings. If using \`FreeBSD date', input DATE strings
must be ISO-8601, \`YYYY-MM-DDThh:mm:ss' unless option \`-f FMT' is
set to a new input time format. If \`date' programme is not avail-
able then input must be ISO-8601 formatted.

If DATE is not set, defaults to \`now'. To flag DATE as UNIX time,
prepend an at sign \`@' to it or set option -r. Stdin input sup-
ports one DATE string per line (max two lines) or two ISO-8601
DATES separated by space in a single line. Input is processed in
a best effort basis.

Output RANGES section displays intervals in different units of
time (years or months or weeks or days or hours or minutes or
seconds alone). It also displays a compound time range with all
the above units into consideration to each other.

Single UNIT time periods can be displayed in table format -t and
their scale set with -NUM where NUM is an integer. Result least
significant digit is subject to rounding. When last positional
parameter UNIT is exactly one of \`Y', \`MO', \`W', \`D', \`H',
\`M' or \`S', only a single UNIT interval is printed.

Output DATE section prints two dates in ISO-8601 format or, if
option -R is set, RFC-5322 format.

Option -e prints Easter date for given YEARs (for western churches).

Option -u sets or prints dates in Coordinated Universal Time (UTC)
in the main function.

Option -l checks if YEAR is leap. Set option -v to decrease ver-
bose. ISO-8601 system assumes proleptic Gregorian calendar, year
zero and no leap seconds.

Option -m prints lunar phase at DATE as \`YYYY[-MM[-DD]]', auto
expansion takes place on partial DATE input. DATE ought to be UTC
time. Code snippet adapted from NetHack.

Option -F prints the date of next Friday the 13th, START_DATE must
be formated as \`YYY[-MM[-DD]]'. Set twice to prints the following
10 matches. Optionally, set a day in the week, such as Sunday, and
a month day number as first and second positional parameters.

ISO-8601 DATE offset is supported throughout this script. When
environment \$TZ is a positive or negative decimal number, such
as \`UTC+3', it is read as offset. Variable \$TZ with timezone name
or ID (e.g. \`America/Sao_Paulo') is supported by \`date' programme.

This script uses Bash/Ksh arithmetics to perform most time range
calculations, as long as input is a valid ISO-8601 date format.

Option -d sets \$TZ=UTC, unsets verbose switches and run checks
against C-code \`datediff' and \`date' (dump only when results
differ), set twice to code exit only.

Option -D disables \`date' package warping and -DD disables Bash/
Ksh \`printf %()T' warping, too.


ENVIRONMENT
TZ Offset time. POSIX time zone definition by the \$TZ vari-
able takes a different form from ISO-8601 standards, so
that ISO UTC-03 is equivalent to setting \$TZ=UTC+03. Only
the \`date' programme can parse timezone names and IDS.


REFINEMENT RULES
Some date intervals can be calculated in more than one way depend-
ing on the logic used in the \`compound time range' display. We
decided to mimic hroptatyr's \`datediff' refinement rules as often
as possible.

Script error rate of the core code is estimated to be lower than
one percent after extensive testing with selected and corner-case
sample dates and times. Check script source code for details.


SEE ALSO
\`Datediff' from \`dateutils', by Hroptatyr.
<www.fresse.org/dateutils/>

\`Units' from GNU.
<https://www.gnu.org/software/units/>

Do calendrical savants use calculation to answer date questions?
A functional magnetic resonance imaging study, Cowan and Frith, 2009.
<https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2677581/#!po=21.1864>

Calendrical calculation, Dershowitz and Reingold, 1990
<http://www.cs.tau.ac.il/~nachum/papers/cc-paper.pdf>
<https://books.google.com.br/books?id=DPbx0-qgXu0C>

How many days are in a year? Manning, 1997.
<https://pumas.nasa.gov/files/04_21_97_1.pdf>

Iana Time zone database
<https://www.iana.org/time-zones>

Fun with Date Arithmetic (see replies)
<https://linuxcommando.blogspot.com/2009/11/fun-with-date-arithmetic.html>

Tip: Division is but subtractions and multiplication but additions.
--Lost reference


WARRANTY
Licensed under the GNU General Public License 3 or better. This
software is distributed without support or bug corrections. Many
thanks for all whose advice improved this script from c.u.shell.

Bash2.05b+ or Ksh93u+ is required. \`Bc' or Ksh is required for
single-unit calculations. FreeBSD12+ or GNU \`date' is option-
ally required.


EXAMPLES
Leap year check
$ ${0##*/} -l 2000
$ ${0##*/} -l {1980..2000}
$ echo 2000 | ${0##*/} -l

Moon phases for January 1996
$ ${0##*/} -m 1996-01

Print following Friday, 13th
$ ${0##*/} -F
Print following Sunday, 12th after 1999
$ ${0##*/} -F sun 12 1999

Single unit time periods
$ ${0##*/} 2022-03-01T00:00:00 2022-03-01T10:10:10 m #(m)ins
$ ${0##*/} '10 years ago' mo #(mo)nths
$ ${0##*/} 1970-01-01 2000-02-02 y #(y)ears

Time ranges/intervals
$ ${0##*/} 2020-01-03T14:30:10 2020-12-24T00:00:00
$ ${0##*/} 0921-04-12 1999-01-31
$ echo 1970-01-01 2000-02-02 | ${0##*/}
$ TZ=UTC+3 ${0##*/} 2020-01-03T14:30:10-06 2021-12-30T21:00:10-03:20

\`GNU date' warping
$ ${0##*/} 'next monday'
$ ${0##*/} 2019/6/28 1Aug
$ ${0##*/} '5min 34seconds'
$ ${0##*/} 1aug1990-9month now
$ ${0##*/} -- -2week-3day
$ ${0##*/} -- \"today + 1day\" @1952292365
$ ${0##*/} -2 -- '1hour ago 30min ago'
$ ${0##*/} today00:00 '12 May 2020 14:50:50'
$ ${0##*/} '2020-01-01 - 6months' 2020-01-01
$ ${0##*/} '05 jan 2005' 'now - 43years -13 days'
$ ${0##*/} @1561243015 @1592865415

\`BSD date' warping
$ ${0##*/} -f'%m/%d/%Y' 6/28/2019 9/04/1970
$ ${0##*/} -r 1561243015 1592865415
$ ${0##*/} 200002280910.33 0003290010.00
$ ${0##*/} -- '-v +2d' '-v -3w'


OPTIONS
-[0-9] Set scale for single unit intervals.
-DDdd Debug, check help page.
-e Print Western Easter date.
-FF Print following Friday the 13th date.
-f FMT Input time format string (only with BSD \`date').
-h Print this help page.
-l Check if YEAR is leap year.
-m Print lunar phase at DATE (ISO UTC time).
-R Print human time in RFC-5322 format (verbose).
-r, -@ Input DATES are UNIX times.
-tt Table layout display of single unit intervals.
-u Set or print UTC time instead of local time.
-vvv Verbose level, change print layout of functions."

#TESTING RESULTS
#!# MAIN TESTING SCRIPT: <https://pastebin.com/suw4Bif3>
# Hroptatyr's `man datediff' says ``refinement rules'' cover over 99% cases.
# Calculated C-code `datediff' error rate is at least 0.26% of total tested dates (compound range).
# Results differ from C-code `datediff' in the ~0.6% of all tested dates in script v0.21 (compound range).
# All differences occur with ``end-of-month vs. start-of-month'' dates, such as days `29, 30 or 31' of one date against days `1, 2 or 3' of the other date.
# Different results from C-code `datediff' in compound range are not necessarily errors in all cases and may be considered correct albeit with different refinements. This seems to be the case for most, if not all, other differences obtained in testing results.
# A bug was fixed in v0.20 in which UNIX time generationw was affected. No errors were found in range (seconds) calculation since.
#!# OFFSET AND $TZ TESTING SCRIPT: <https://pastebin.com/ZXnHLrY8>
# Note `datediff' offset ranges between -14h and +14h.
# Offset-aware date results passed checking against `datediff' as of v0.21.
#Ksh exec time is ~2x faster than Bash (main function).

#NOTES
##Time zone / Offset support
#dbplunkett: <https://stackoverflow.com/questions/38641982/converting-date-between-timezones-swift>
#-00:00 and +24:00 are valid and should equal to +00:00; however -0 is denormal;
#support up to `seconds' for time zone adjustment; POSIX time does not
#account for leap seconds; POSIX time zone definition by the $TZ variable
#takes a different form from ISO8601 standards; environment $TZ applies to both dates;
#it is easier to support OFFSET instead of TIME ZONE; should not support
#STD (standard) or DST (daylight saving time) in timezones, only offsets;
# America/Sao_Paulo is a TIMEZONE ID, not NAME; `Pacific Standard Time' is a tz name.
#<https://stackoverflow.com/questions/3010035/converting-a-utc-time-to-a-local-time-zone-in-java>
#<https://www.iana.org/time-zones>, <https://www.w3.org/TR/NOTE-datetime>
#<https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html>
##A year zero does not exist in the Anno Domini (AD) calendar year system
#commonly used to number years in the Gregorian calendar (nor in its
#predecessor, the Julian calendar); in this system, the year 1 BC is
#followed directly by year AD 1. However, there is a year zero in both
#the astronomical year numbering system (where it coincides with the
#Julian year 1 BC), and the ISO 8601:2004 system, the interchange standard
#for all calendar numbering systems (where year zero coincides with the
#Gregorian year 1 BC). In Proleptic Gregorian calendar, year 0000 is leap.
#<https://docs.julialang.org/en/v1/stdlib/Dates/>
#Serge3leo - https://stackoverflow.com/questions/26861118/rounding-numbers-with-bc-in-bash
#MetroEast - https://askubuntu.com/questions/179898/how-to-round-decimals-using-bc-in-bash
#``Rounding is more accurate than chopping/truncation''.
#https://wiki.math.ntnu.no/_media/ma2501/2016v/lecture1-intro.pdf
##Negative zeros have some subtle properties that will not be evident in
#most programs. A zero exponent with a nonzero mantissa is a "denormal."
#A denormal is a number whose magnitude is too small to be represented
#with an integer bit of 1 and can have as few as one significant bit.
#https://www.lahey.com/float.htm


#globs
SEP='Tt/.:+-'
EPOCH=1970-01-01T00:00:00
GLOBOPT='@(y|mo|w|d|h|m|s|Y|MO|W|D|H|M|S)'
GLOBUTC='*(+|-)@(?([Uu])[Tt][Cc]|?([Uu])[Cc][Tt]|?([Gg])[Mm][Tt]|Z|z)' #see bug ``*?(exp)'' in bash2.05b extglob; [UG] are marked optional for another hack in this script
GLOBTZ="?($GLOBUTC)?(+|-)@(2[0-4]|?([01])[0-9])?(?(:?([0-5])[0-9]|:60)?(:?([0-5])[0-9]|:60)|?(?([0-5])[0-9]|60)?(?([0-5])[0-9]|60))"
GLOBDATE='?(+|-)+([0-9])[/.-]@(1[0-2]|?(0)[1-9])[/.-]@(3[01]|?(0)[1-9]|[12][0-9])'
GLOBTIME="@(2[0-4]|?([01])[0-9]):?(?([0-5])[0-9]|60)?(:?([0-5])[0-9]|:60)?($GLOBTZ)"
#https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html
#custom support for 24h clock and leap second

DAY_OF_WEEK=(Thursday Friday Saturday Sunday Monday Tuesday Wednesday)
MONTH_OF_YEAR=(January February March April May June July August September October November December)
YEAR_MONTH_DAYS=(31 28 31 30 31 30 31 31 30 31 30 31)
TIME_ISO8601_FMT='%Y-%m-%dT%H:%M:%S%z'
TIME_RFC5322_FMT='%a, %d %b %Y %H:%M:%S %z'


# Choose between GNU or BSD date
# datefun.sh [-u|-R|-v[val]|-I[fmt]] [YYY-MM-DD|@UNIX] [+OUTPUT_FORMAT]
# datefun.sh [-u|-R|-v[val]|-I[fmt]]
# By defaults, input should be ISO8601 date or UNIX time (append @).
# Option -I `fmt' may be `date', `hours', `minutes' or `seconds' (added in FreeBSD12).
# Setting environment TZ=UTC is equivalent to -u.
function datefun
{
typeset options unix_input input_fmt globtest ar chars start
input_fmt="${INPUT_FMT:-${TIME_ISO8601_FMT%??}}"
[[ $1 = -[RIv]* ]] && options="$1" && shift

if ((BSDDATE))
then globtest="*([$IFS])@($GLOBDATE?([$SEP])?(+([$SEP])$GLOBTIME)|$GLOBTIME)?([$SEP])*([$IFS])"
[[ ! $1 ]] && set --
if [[ $1 = +([0-9])?(.[0-9][0-9]) && ! $OPTF ]] #default fmt [[[[[cc]yy]mm]dd]HH]MM[.ss]
then ${DATE_CMD} ${options} -j "$@" && return
elif [[ $1 = $globtest && ! $OPTF ]] #ISO8601 variable length
then ar=(${1//[$SEP]/ })
[[ ${1//[$IFS]} = +([0-9])[:]* ]] && start=9 || start=0
((chars=(${#ar[@]}*2)+(${#ar[@]}-1) ))
${DATE_CMD} ${options} -j -f "${TIME_ISO8601_FMT:start:chars}" "${@/$GLOBUTC}" && return
fi
[[ ${1:-+%} != @(+%|@|-f)* ]] && set -- -f"${input_fmt}" "$@"
[[ $1 = @* ]] && set -- "-r${1#@}" "${@:2}"
${DATE_CMD} ${options} -j "$@"
else
[[ ${1:-+%} != @(+%|-d)* ]] && set -- -d"${unix_input}${1}" "${@:2}"
${DATE_CMD} ${options} "$@"
fi
}

#leap fun
function is_leapyear
{
((!($1 % 4) && ($1 % 100 || !($1 % 400) ) ))
}

#print the maximum number of days of a given month
#usage: month_maxday [MONTH] [YEAR]
#MONTH range 1-12; YEAR cannot be nought.
function month_maxday
{
typeset month year
month="$1" year="$2"
if ((month==2)) && is_leapyear $year
then echo 29
else echo ${YEAR_MONTH_DAYS[month-1]}
fi
}

#year days, leap years only if date1's month is before or at feb.
function year_days_adj
{
typeset month year
month="$1" year="$2"
if ((month<=2)) && is_leapyear $year
then echo 366
else echo 365
fi
}

#check if input is an integer year
function is_year
{
if [[ $1 = +([0-9]) ]]
then return 0
else printf 'err: year must be in the format YYYY -- %s\n' "$1" >&2
fi
return 1
}

#verbose check if year is leap
function is_leapyear_verbose
{
typeset year
year="$1"
if is_leapyear $year
then ((OPTVERBOSE)) || printf 'leap year -- %04d\n' $year
else ((OPTVERBOSE)) || printf 'not leap year -- %04d\n' $year
false
fi
}
#https://stackoverflow.com/questions/32196629/my-shell-script-for-checking-leap-year-is-showing-error

#check Easter date in a given year
function easterf
{
echo ${*:?year required} '[ddsf[lfp[too early
]Pq]s@1583>@
ddd19%1+sg100/1+d3*4/12-sx8*5+25/5-sz5*4/lx-10-sdlg11*20+lz+lx-30%
d[30+]s@0>@d[[1+]s@lg11<@]s@25=@d[1+]s@24=@se44le-d[30+]s@21>@dld+7%-7+
[March ]smd[31-[April ]sm]s@31<@psnlmPpsn1z>p]splpx' | dc
}
#Dershowitz' and Reingold' Calendrical Calculations book

#get day in the week
#usage: get_day_in_week unix_time
function get_day_in_week
{
echo ${DAY_OF_WEEK[( ( ($1+($1<0?1:0))/(24*60*60))%7 +($1<0?6:7))%7]}
}

#get day in the year
#usage: get_day_in_year year month day
function get_day_in_year
{
typeset day month year month_test daysum
day="${1#0}" month="${2#0}" year="${3##+(0)}"
for ((month_test=1;month_test<month;++month_test))
do ((daysum+=$(month_maxday "$month_test" "$year")))
done
echo $((day+daysum))
}

#return phase of the moon, use UTC time
#usage: phase_of_the_moon year [month] [day]
function phase_of_the_moon #0-7, with 0: new, 4: full
{
typeset day month year diy goldn epact
day="${1#0}" month="${2#0}" year="${3##+(0)}"
day=${day:-1} month=${month:-1} year=${year:-0}

diy=$(get_day_in_year "$day" "$month" "$year")
((goldn = (year % 19) + 1))
((epact = (11 * goldn + 18) % 30))
(((epact == 25 && goldn > 11) || epact == 24 )) && ((epact++))

case $(( ( ( ( ( (diy + epact) * 6) + 11) % 177) / 22) & 7)) in
0) set -- 'New Moon' ;; #.0
1) set -- 'Waxing Crescent' ;;
2) set -- 'First Quarter' ;; #.25
3) set -- 'Waxing Gibbous' ;;
4) set -- 'Full Moon' ;; #.5
5) set -- 'Waning Gibbous' ;;
6) set -- 'Last Quarter' ;; #.75
7) set -- 'Waning Crescent' ;;
esac
#Bash's integer division truncates towards zero, as in C.
[[ $*${OPTM#2} = $PHASE_SKIP ]] && return || PHASE_SKIP="$*"
if ((OPTVERBOSE))
then printf '%s\n' "$*"
else printf '%04d-%02d-%02d %s\n' "$year" "$month" "$day" "$*"
fi
}
#<https://nethack.org/>
#<https://aa.usno.navy.mil/data/MoonPhases>
#<https://aa.usno.navy.mil/faq/moon_phases>
#<http://astropixels.com/ephemeris/phasescat/phases1901.html>
#<https://www.nora.ai/competition/fishai-dataset-competition/about-the-dataset/>
#<https://www.kaggle.com/datasets/lsind18/full-moon-calendar-1900-2050>
#<https://www.fullmoon.info/en/fullmoon-calendar_1900-2050.html>

#get current time
#usage: get_timef [unix_time] [print_format]
function get_timef
{
typeset fmt
fmt="${2:-${TIME_ISO8601_FMT}}"
if ((OPTDD))
then echo $EPOCH ;false
elif [[ $ZSH_VERSION ]]
then zmodload -aF zsh/datetime b:strftime && strftime "$fmt" $1
else printf "%(${fmt})T\n" ${BASH_VERSION:+${1:--1}}
fi
}

#get friday 13th dates
#usage: friday_13th [weekday_name] [day] [start_year]
function friday_13th
{
typeset dow_name d_tgt diw_tgt day month year unix diw maxday skip n
dow_name=("${DAY_OF_WEEK[@]}") ;DAY_OF_WEEK=(0 1 2 3 4 5 6)

#set day of week and day of month
[[ $2 = [SsMmTtWwFf]* && $1 = ?([0-3])[0-9] ]] && set -- "$2" "$1" "${@:3}"
if [[ $1 = [SsMmTtWwFf]* && $2 = ?([0-3])[0-9] ]]
then case $1 in
[Ss][Aa]*) diw_tgt=${DAY_OF_WEEK[2]};;
[Ff]*) diw_tgt=${DAY_OF_WEEK[1]};;
[Tt]*) diw_tgt=${DAY_OF_WEEK[0]};;
[Ww]*) diw_tgt=${DAY_OF_WEEK[6]};;
[Tt][Uu]*) diw_tgt=${DAY_OF_WEEK[5]};;
[Mm]*) diw_tgt=${DAY_OF_WEEK[4]};;
[Ss]*) diw_tgt=${DAY_OF_WEEK[3]};;
esac
d_tgt=$2 ;shift 2
fi ;diw_tgt=${diw_tgt:-1} d_tgt=${d_tgt:-13}

[[ $1 ]] || set -- $(get_timef) ;set -- ${*//[$SEP]/ }
day="${3#0}" month="${2#0}" year="${1##+(0)}"
day="${day:-1}" month="${month:-1}" year="${year:-0}"

unix=$(GETUNIX=1 OPTVERBOSE=1 OPTRR= TZ=UTC \
mainf $EPOCH ${year}-${month}-${day}) || return $?

while diw=$(get_day_in_week $((unix+(d_away*24*60*60) )) )
do if ((diw==diw_tgt && day==d_tgt))
then if ((!(d_away+OPTVERBOSE+OPTFF-1) ))
then printf "%s, %02d %s %04d is today!\n" \
"${dow_name[diw_tgt]:0:3}" "$day" "${MONTH_OF_YEAR[month-1]:0:3}" "$year"
elif ((OPTVERBOSE))
then printf "%04d-%02d-%02d\n" "$year" "$month" "$day"
else printf "%s, %02d %s %04d is %4d days ahead\n" \
"${dow_name[diw_tgt]:0:3}" "$day" "${MONTH_OF_YEAR[month-1]:0:3}" "$year" "$d_away"
fi
((++n))
((OPTFF==1||(OPTFF==2&&n>=10) )) && break
fi
maxday=$(month_maxday $month $year)
if ((day<d_tgt))
then ((d_away=d_tgt-day, day=d_tgt, skip=1))
elif ((day>d_tgt))
then ((d_away=(maxday-day+d_tgt), day=d_tgt))
else ((d_away+=maxday))
fi
if ((!skip))
then ((month==12)) && ((++year))
((month=(month==12?1:month+1) ))
fi ;skip=
done
}

#printing helper
#(A). check if floating point in $1 is `>0', set return signal and $SS to `s' when `>1.0'.
#usage: prHelpf 1.23
#(B). set padding of $1 length until [max] chars and set $SSS.
#usage: prHelpf 1.23 [max]
function prHelpf
{
typeset val valx int dec x z
#(B)
if (($#>1))
then SSS= x=$(( ${2} - ${#1} ))
for ((z=0;z<x;++z))
do SSS="$SSS "
done
fi

#(A)
SS= val=${1#-} val=${val#0} valx=${val//[0.]} int=${val%.*}
[[ $val = *.* ]] && dec=${val#*.} dec=${dec//0}
[[ $1 && $OPTT ]] || ((valx)) || return
(( int>1 || ( (int==1) && (dec) ) )) && SS=s
return 0
}

#datediff fun
function mainf
{
${DEBUG:+unset} typeset date1_iso8601 date2_iso8601 unix1 unix2 inputA inputB range neg_range yearA monthA dayA hourA minA secA tzA neg_tzA tzAh tzAm tzAs yearB monthB dayB hourB minB secB tzB neg_tzB tzBh tzBm tzBs years_between y_test leapcount daycount_leap_years daycount_years fullmonth_days fullmonth_days_save monthcount month_test month_tgt d1_mmd d2_mmd date1_month_max_day date3_month_max_day date1_year_days_adj d_left y mo w d h m s bc bcy bcmo bcw bcd bch bcm range_pr sh d_left_save d_sum date1_iso8601_pr date2_iso8601_pr yearAtz monthAtz dayAtz hourAtz minAtz secAtz yearBtz monthBtz dayBtz hourBtz minBtz secBtz yearAprtz monthAprtz dayAprtz hourAprtz minAprtz secAprtz yearBprtz monthBprtz dayBprtz hourBprtz minBprtz secBprtz range_check now badges date1_diw date2_diw prfmt varname buf var ok ar ret n p q r v TZh TZm TZs TZ_neg TZ_pos spcr #SS SSS

(($# == 1)) && set -- '' "$1"

#warp `date' when available
if unix1=$(datefun "${1:-+%s}" ${1:++%s}) &&
unix2=$(datefun "${2:-+%s}" ${2:++%s})
then ((GETUNIX)) && { echo $((unix1+unix2)) ;unset GETUNIX ;return ${ret:-0} ;}
#sort dates
if ((unix1 > unix2))
then buf=$unix2 unix2=$unix1 unix1=$buf neg_range=-1
set -- "$2" "$1" "${@:3}"
fi
{
date1_iso8601=$(datefun -Iseconds @"$unix1")
date2_iso8601=$(datefun -Iseconds @"$unix2")
if [[ ! $OPTVERBOSE && $OPTRR ]]
then date1_iso8601_pr=$(datefun -R @"$unix1")
date2_iso8601_pr=$(datefun -R @"$unix2")
fi
} 2>/dev/null #avoid printing errs from FreeBSD<12 `date'
else unset unix1 unix2
#set default date -- AD
[[ ! $1 || ! $2 ]] && now=$(get_timef)
[[ ! $1 ]] && { set -- "${now}" "${@:2}" ;date1_iso8601="$now" ;}
[[ ! $2 ]] && { set -- "$1" "${now}" "${@:3}" ;date2_iso8601="$now" ;}
fi

#load ISO8601 dates from `date' or user input
inputA="${date1_iso8601:-$1}" inputB="${date2_iso8601:-$2}"
if [[ ! $unix2 ]] #time only input, no `date' pkg available
then [[ $inputA = *([0-9]):* ]] && inputA="${EPOCH:0:10}T${inputA}"
[[ $inputB = *([0-9]):* ]] && inputB="${EPOCH:0:10}T${inputB}"
fi
IFS="${IFS}${SEP}UuGgZz" read yearA monthA dayA hourA minA secA tzA <<<"${inputA##*(+|-)}"
IFS="${IFS}${SEP}UuGgZz" read yearB monthB dayB hourB minB secB tzB <<<"${inputB##*(+|-)}"
IFS="${IFS}${SEP/[Tt]}" read tzAh tzAm tzAs var <<<"${tzA##?($GLOBUTC?(+|-)|[+-])}"
IFS="${IFS}${SEP/[Tt]}" read tzBh tzBm tzBs var <<<"${tzB##?($GLOBUTC?(+|-)|[+-])}"
IFS="${IFS}${SEP/[Tt]}" read TZh TZm TZs var <<<"${TZ##?($GLOBUTC?(+|-)|[+-])}"

#fill in some defaults
monthA=${monthA:-1} dayA=${dayA:-1} monthB=${monthB:-1} dayB=${dayB:-1}
#support offset as `[+-]XXXX??'
[[ $tzAh = [0-9][0-9][0-9][0-9]?([0-9][0-9]) ]] \
&& tzAs=${tzAh:4:2} tzAm=${tzAh:2:2} tzAh=${tzAh:0:2}
[[ $tzBh = [0-9][0-9][0-9][0-9]?([0-9][0-9]) ]] \
&& tzBs=${tzBh:4:2} tzBm=${tzBh:2:2} tzBh=${tzBh:0:2}
[[ ${TZh} = [0-9][0-9][0-9][0-9]?([0-9][0-9]) ]] \
&& TZs=${TZh:4:2} TZm=${TZh:2:2} TZh=${TZh:0:2}

#set parameters as decimals ASAP
for varname in yearA monthA dayA hourA minA secA \
yearB monthB dayB hourB minB secB \
tzAh tzAm tzAs tzBh tzBm tzBs TZh TZm TZs
do eval "[[ \${$varname} = *[A-Za-z_]* ]] && continue" #avoid printing errs
eval "(($varname=\${$varname//[!+-]}10#0\${$varname#[+-]}))"
done

#negative years
[[ $inputA = -?* ]] && yearA=-$yearA
[[ $inputB = -?* ]] && yearB=-$yearB
#
#iso8601 date string offset
[[ ${inputA%"${tzA##?($GLOBUTC?(+|-)|[+-])}"} = *?- ]] && neg_tzA=-1 || neg_tzA=+1
[[ ${inputB%"${tzB##?($GLOBUTC?(+|-)|[+-])}"} = *?- ]] && neg_tzB=-1 || neg_tzB=+1
((tzAh==0 && tzAm==0 && tzAs==0)) && neg_tzA=+1
((tzBh==0 && tzBm==0 && tzBs==0)) && neg_tzB=+1
#
#environment $TZ
[[ ${TZ##*$GLOBUTC} = -?* ]] && TZ_neg=-1 || TZ_neg=+1
((TZh==0 && TZm==0 && TZs==0)) && TZ_neg=+1
((TZ_neg<0)) && TZ_pos=+1 || TZ_pos=-1
[[ $TZh$TZm$TZs = *([0-9+-]) && ! $unix2 ]] || unset TZh TZm TZs

#24h clock and input leap second support (these $tz* parameters will be zeroed later)
((hourA==24)) && (( (neg_tzA>0 ? (tzAh-=hourA-23) : (tzAh+=hourA-23) ) , (hourA-=hourA-23) ))
((hourB==24)) && (( (neg_tzB>0 ? (tzBh-=hourB-23) : (tzBh+=hourB-23) ) , (hourB-=hourB-23) ))
((minA==60)) && (( (neg_tzA>0 ? (tzAm-=minA-59) : (tzAm+=minA-59) ) , (minA-=minA-59) ))
((minB==60)) && (( (neg_tzB>0 ? (tzBm-=minB-59) : (tzBm+=minB-59) ) , (minB-=minB-59) ))
((secA==60)) && (( (neg_tzA>0 ? (tzAs-=secA-59) : (tzAs+=secA-59) ) , (secA-=secA-59) ))
((secB==60)) && (( (neg_tzB>0 ? (tzBs-=secB-59) : (tzBs+=secB-59) ) , (secB-=secB-59) ))
#CHECK SCRIPT `GLOBS', TOO, as they may fail with weyrd dates and formats.

#check input validity
d1_mmd=$(month_maxday "$monthA" "$yearA") ;d2_mmd=$(month_maxday "$monthB" "$yearB")
if ! (( (yearA||yearA==0) && (yearB||yearB==0) && monthA && monthB && dayA && dayB )) ||
((
monthA>12 || monthB>12 || dayA>d1_mmd || dayB>d2_mmd
|| hourA>23 || hourB>23 || minA>59 || minB>59 || secA>59 || secB>59
))
then echo "err: illegal user input -- ISO-8601 DATE required" >&2 ;return 2
fi

#offset and $TZ support
if ((tzAh||tzAm||tzAs||tzBh||tzBm||tzBs||TZh||TZm||TZs))
then #check validity
if ((tzAh>24||tzBh>24||tzAm>60||tzBm>60||tzAs>60||tzBs>60))
then echo "warning: illegal offsets" >&2
unset tzA tzB tzAh tzAm tzAs tzBh tzBm tzBs
fi
if ((TZh>23||TZm>59||TZs>59))
then echo "warning: illegal environment \$TZ" >&2
unset TZh TZm TZs
fi #offset specs:
#<https://www.w3.org/TR/NOTE-datetime>
#<https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html>

#environment $TZ support #only for printing
if ((!OPTVERBOSE)) && ((TZh||TZm||TZs))
then ((hourAprtz-=(TZh*TZ_neg), minAprtz-=(TZm*TZ_neg), secAprtz-=(TZs*TZ_neg) ))
((hourBprtz-=(TZh*TZ_neg), minBprtz-=(TZm*TZ_neg), secBprtz-=(TZs*TZ_neg) ))
[[ ! $tzA ]] && ((tzAh-=(TZh*TZ_neg), tzAm-=(TZm*TZ_neg), tzAs-=(TZs*TZ_neg) ))
[[ ! $tzB ]] && ((tzBh-=(TZh*TZ_neg), tzBm-=(TZm*TZ_neg), tzBs-=(TZs*TZ_neg) ))
else unset TZh TZm TZs
fi

#convert dates to UTC for internal range calculations
((tzAh||tzAm||tzAs)) && var="A" || var=""
((tzBh||tzBm||tzBs)) && var="$var B"
((TZh||TZm||TZs)) && var="$var A.pr B.pr"
for v in $var #A B A.pr B.pr
do
[[ $v = ?.* ]] && p=${v#*.} v=${v%.*} || p=

#secAtz secBtz secAprtz secBprtz
((sec${v}${p}tz=sec${v}-(tz${v}s*neg_tz${v}) )) #neg_tzA neg_tzB
if ((sec${v}${p}tz<0))
then ((min${v}${p}tz+=((sec${v}${p}tz-59)/60) , sec${v}${p}tz=(sec${v}${p}tz%60+60)%60))
elif ((sec${v}${p}tz>59))
then ((min${v}${p}tz+=(sec${v}${p}tz/60) , sec${v}${p}tz%=60))
fi

#minAtz minBtz minAprtz minBprtz
((min${v}${p}tz+=min${v}-(tz${v}m*neg_tz${v}) ))
if ((min${v}${p}tz<0))
then ((hour${v}${p}tz+=((min${v}${p}tz-59)/60) , min${v}${p}tz=(min${v}${p}tz%60+60)%60))
elif ((min${v}${p}tz>59))
then ((hour${v}${p}tz+=(min${v}${p}tz/60) , min${v}${p}tz%=60))
fi

#hourAtz hourBtz hourAprtz hourBprtz
((hour${v}${p}tz+=hour${v}-(tz${v}h*neg_tz${v}) ))
if ((hour${v}${p}tz<0))
then ((day${v}${p}tz+=((hour${v}${p}tz-23)/24) , hour${v}${p}tz=(hour${v}${p}tz%24+24)%24))
elif ((hour${v}${p}tz>23))
then ((day${v}${p}tz+=(hour${v}${p}tz/24) , hour${v}${p}tz%=24))
fi

#dayAtz dayBtz dayAprtz dayBprtz
((day${v}${p}tz+=day${v}))
if ((day${v}${p}tz<1))
then var=$(month_maxday "$((month${v}==1 ? 12 : month${v}-1))" "$((year${v}))")
((day${v}${p}tz+=var))
if ((month${v}>1))
then ((--month${v}${p}tz))
else ((month${v}${p}tz-=month${v}))
fi
elif var=$(month_maxday "$((month${v}))" "$((year${v}))")
((day${v}${p}tz>var))
then ((++month${v}${p}tz))
((day${v}${p}tz%=var))
fi

#monthAtz monthBtz monthAprtz monthBprtz
((month${v}${p}tz+=month${v}))
if ((month${v}${p}tz<1))
then ((--year${v}${p}tz))
((month${v}${p}tz+=12))
elif ((month${v}${p}tz>12))
then ((++year${v}${p}tz))
((month${v}${p}tz%=12))
fi

((year${v}${p}tz+=year${v})) #yearAtz yearBtz yearAprtz yearBprtz
done
#modulus as (a%b + b)%b to avoid negative remainder.
#<https://www.geeksforgeeks.org/modulus-on-negative-numbers/>

if [[ $yearAtz ]]
then (( yearA=yearAtz , monthA=monthAtz , dayA=dayAtz,
hourA=hourAtz , minA=minAtz , secA=secAtz ,
tzAh=0 , tzAm=0 , tzAs=0
))
fi
if [[ $yearBtz ]]
then (( yearB=yearBtz , monthB=monthBtz , dayB=dayBtz,
hourB=hourBtz , minB=minBtz , secB=secBtz ,
tzBh=0 , tzBm=0 , tzBs=0
))
fi

if [[ $yearAprtz ]]
then date1_iso8601_pr=$(printf \
%04d-%02d-%02dT%02d:%02d:%02d%s%02d:%02d:%02d\\n \
"$yearAprtz" "$monthAprtz" "${dayAprtz}" \
"${hourAprtz}" "${minAprtz}" "${secAprtz}" \
"${TZ_pos%1}" "$TZh" "$TZm" "$TZs")
fi
if [[ $yearBprtz ]]
then date2_iso8601_pr=$(printf \
%04d-%02d-%02dT%02d:%02d:%02d%s%02d:%02d:%02d\\n \
"$yearBprtz" "$monthBprtz" "${dayBprtz}" \
"${hourBprtz}" "${minBprtz}" "${secBprtz}" \
"${TZ_pos%1}" "$TZh" "$TZm" "$TZs")
fi

elif [[ ! $unix2$OPTVERBOSE && $tzA$tzB$TZ = *+([A-Za-z_])* ]]
then #echo "warning: input DATE or \$TZ contains timezone ID or name. Support requires package \`date'" >&2
unset tzA tzB tzAh tzBh tzAm tzBm tzAs tzBs TZh TZm TZs
else unset tzA tzB tzAh tzBh tzAm tzBm tzAs tzBs TZh TZm TZs
fi #Offset is *from* UTC, while $TZ is *to* UTC.


#sort `UTC' dates (if no `date' package)
if [[ ! $unix2 ]] && ((
(yearA>yearB)
|| ( (yearA==yearB) && (monthA>monthB) )
|| ( (yearA==yearB) && (monthA==monthB) && (dayA>dayB) )
|| ( (yearA==yearB) && (monthA==monthB) && (dayA==dayB) && (hourA>hourB) )
|| ( (yearA==yearB) && (monthA==monthB) && (dayA==dayB) && (hourA==hourB) && (minA>minB) )
|| ( (yearA==yearB) && (monthA==monthB) && (dayA==dayB) && (hourA==hourB) && (minA==minB) && (secA>secB) )
))
then neg_range=-1
for varname in inputA yearA monthA dayA hourA minA secA \
yearAtz monthAtz dayAtz hourAtz minAtz secAtz \
yearAprtz monthAprtz dayAprtz hourAprtz minAprtz secAprtz \
tzA tzAh tzAm tzAs neg_tzA date1_iso8601 date1_iso8601_pr
do #swap $varA/$varB or $var1/$var2 values
[[ $varname = *A* ]] && p=A q=B || p=1 q=2
eval "buf=\"\$$varname\""
eval "$varname=\"\$${varname/$p/$q}\" ${varname/$p/$q}=\"\$buf\""
done
unset varname p q
set -- "$2" "$1" "${@:3}"
fi


##Count leap years and sum leap and non leap years days,
for ((y_test=(yearA+1);y_test<yearB;++y_test))
do
#((y_test==0)) && continue #ISO8601 counts year zero, proleptic gregorian/julian do not
is_leapyear $y_test && ((++leapcount))
((++years_between))
((monthcount += 12))
done
##count days in non and leap years
(( daycount_leap_years = (366 * leapcount) ))
(( daycount_years = (365 * (years_between - leapcount) ) ))

#date2 days so far this year (this month)
#days in prior months `this' year
((month_tgt = (yearA==yearB ? monthA : 0) ))
for ((month_test=(monthB-1);month_test>month_tgt;--month_test))
do
if ((month_test==2)) && is_leapyear $yearB
then (( fullmonth_days += 29 ))
else (( fullmonth_days += ${YEAR_MONTH_DAYS[month_test-1]} ))
fi
((++monthcount))
done

#date1 days until end of `that' year
#days in prior months `that' year
((yearA==yearB)) ||
for ((month_test=(monthA+1);month_test<13;++month_test))
do
if ((month_test==2)) && is_leapyear $yearA
then (( fullmonth_days += 29 ))
else (( fullmonth_days += ${YEAR_MONTH_DAYS[month_test-1]} ))
fi
((++monthcount))
done
((fullmonth_days_save = fullmonth_days))

#some info about input dates and their context..
date3_month_max_day=$(month_maxday "$((monthB==1 ? 12 : monthB-1))" "$yearB")
date1_month_max_day=$(month_maxday "$monthA" "$yearA")
date1_year_days_adj=$(year_days_adj "$monthA" "$yearA")


#set years and months
(( y = years_between ))
(( mo = ( monthcount - ( (years_between) ? (years_between * 12) : 0) ) ))

#days left
if ((yearA==yearB && monthA==monthB))
then
((d_left = (dayB - dayA) ))
((d_left_save = d_left))
elif ((dayA<dayB))
then
((++mo))
((fullmonth_days += date1_month_max_day))
((d_left = (dayB - dayA) ))
((d_left_save = d_left))
elif ((dayA>dayB))
then #refinement rules (or hacks)
((d_left = ( (date3_month_max_day>=dayA) ? (date3_month_max_day-dayA) : (date1_month_max_day-dayA) ) + dayB ))
((d_left_save = (date1_month_max_day-dayA) + dayB ))
if ((dayA>date3_month_max_day && date3_month_max_day<date1_month_max_day && dayB>1))
then
((dayB>=dayA-date3_month_max_day)) && ##addon2 -- prevents negative days
((d_left -= date1_month_max_day-date3_month_max_day))
((d_left==0 && ( (24-hourA)+hourB<24 || ( (24-hourA)+hourB==24 && (60-minA)+minB<60 ) || ( (24-hourA)+hourB==24 && (60-minA)+minB==60 && (60-secA)+secB<60 ) ) && (++d_left) )) ##addon3 -- prevents breaking down a full month
if ((d_left < 0))
then if ((w))
then ((--w , d_left+=7))
elif ((mo))
then ((--mo , w=date3_month_max_day/7 , d_left+=date3_month_max_day%7))
elif ((y))
then ((--y , mo+=11 , w=date3_month_max_day/7 , d_left+=date3_month_max_day%7))
fi
fi
elif ((dayA>date3_month_max_day)) #dayB==1
then
((d_left = (date1_month_max_day - dayA + date3_month_max_day + dayB) ))
((w = d_left/7 , d_left%=7))
if ((mo))
then ((--mo))
elif ((y))
then ((--y , mo+=11))
fi
fi
else #`dayA' equals `dayB'
((++mo))
((fullmonth_days += date1_month_max_day))
#((d_left_save = d_left)) #set to 0
fi


((h += (24-hourA)+hourB))
if ((h && h<24))
then if ((d_left))
then ((--d_left , ++ok))
elif ((mo))
then ((--mo , d_left+=date3_month_max_day-1 , ++ok))
elif ((y))
then ((--y , mo+=11 , d_left+=date3_month_max_day-1 , ++ok))
fi
fi
((h %= 24))

((m += (60-minA)+minB))
if ((m && m<60))
then if ((h))
then ((--h))
elif ((d_left))
then ((--d_left , h+=23 , ++ok))
elif ((mo))
then ((--mo , d_left+=date3_month_max_day-1 , h+=23 , ++ok))
elif ((y))
then ((--y , mo+=11 , d_left+=date3_month_max_day-1 , h+=23 , ++ok))
fi
fi
((m %= 60))

((s = (60-secA)+secB))
if ((s && s<60))
then if ((m))
then ((--m))
elif ((h))
then ((--h , m+=59))
elif ((d_left))
then ((--d_left , h+=23 , m+=59 , ++ok))
elif ((mo))
then ((--mo , d_left+=date3_month_max_day-1 , h+=23 , m+=59 , ++ok))
elif ((y))
then ((--y , mo+=11 , d_left+=date3_month_max_day-1 , h+=23 , m+=59 , ++ok))
fi
fi
((s %= 60))
((ok && (--d_left_save) ))

((m += s/60 , s %= 60))
((h += m/60 , m %= 60))
((d_left_save += h/24))
((d_left += h/24 , h %= 24))
((y += mo/12 , mo %= 12))
((w += d_left/7))
((d = d_left%7))


#total sum of full days { range = unix2-unix1 }
((d_sum = ( (d_left_save) + (fullmonth_days + daycount_years + daycount_leap_years) ) ))
((range = (d_sum * 3600 * 24) + (h * 3600) + (m * 60) + s))

#generate unix times arithmetically?
((GETUNIX)) && { echo ${neg_range%1}${range} ;unset GETUNIX ;return ${ret:-0} ;}
if [[ ! $unix2 ]]
then badges="$badges#"
if ((
(yearA>1970 ? yearA-1970 : 1970-yearA)
> (yearB>1970 ? yearB-1970 : 1970-yearB)
))
then var=$yearB-$monthB-${dayB}T$hourB:$minB:$secB varname=B #utc times
else var=$yearA-$monthA-${dayA}T$hourA:$minA:$secA varname=A
fi

var=$(GETUNIX=1 DATE_CMD=false OPTVERBOSE=1 OPTRR= TZ= \
mainf $EPOCH $var) || ((ret+=$?))

if [[ $varname = B ]]
then ((unix2=var , unix1=unix2-range))
else ((unix1=var , unix2=unix1+range))
fi

if ((OPTRR)) #make RFC-5322 format string
then if ! { date2_iso8601_pr=$(get_timef "$unix2" "$TIME_RFC5322_FMT") &&
date1_iso8601_pr=$(get_timef "$unix1" "$TIME_RFC5322_FMT") ;}
then #calculate Day Of Week (bash v<3.1)
date2_diw=$(get_day_in_week $((unix2-( ( (TZh*60*60)+(TZm*60)+TZs)*TZ_neg) )) )
date1_diw=$(get_day_in_week $((unix1-( ( (TZh*60*60)+(TZm*60)+TZs)*TZ_neg) )) )
date2_iso8601_pr=$(printf \
'%s, %02d %s %04d %02d:%02d:%02d %s%02d:%02d:%02d\n' \
"${date2_diw:0:3}" "${dayBprtz:-${dayBtz:-$dayB}}" \
"${MONTH_OF_YEAR[${monthBprtz:-${monthBtz:-$monthB}}-1]:0:3}" \
"${yearBprtz:-${yearBtz:-$yearB}}" \
"${hourBprtz:-${hourBtz:-$hourB}}" \
"${minBprtz:-${minBtz:-$minB}}" \
"${secBprtz:-${secBtz:-$secB}}" \
"${TZ_pos%1}" "$TZh" "$TZm" "$TZs")
date1_iso8601_pr=$(printf \
'%s, %02d %s %04d %02d:%02d:%02d %s%02d:%02d:%02d\n' \
"${date1_diw:0:3}" "${dayAprtz:-${dayAtz:-$dayA}}" \
"${MONTH_OF_YEAR[${monthAprtz:-${monthAtz:-$monthA}}-1]:0:3}" \
"${yearAprtz:-${yearAtz:-$yearA}}" \
"${hourAprtz:-${hourAtz:-$hourA}}" \
"${minAprtz:-${minAtz:-$minA}}" \
"${secAprtz:-${secAtz:-$secA}}" \
"${TZ_pos%1}" "$TZh" "$TZm" "$TZs")
fi
fi
fi

#single unit time durations (when `bc' is available)
if ((OPTT || OPTVERBOSE<3))
then if [[ $BASH_VERSION ]]
then bc=( $(bc <<<" /* round argument 'x' to 'd' digits */
define r(x, d) { auto r, s; if(0 > x) { return -r(-x, d); };
r = x + 0.5*10^-d; s = scale; scale = d; r = r*10/10;
scale = s; return r; }; scale = ($SCL + 1);
r( (${years_between:-0} + ( (${range:-0} - ( (${daycount_years:-0} + ${daycount_leap_years:-0}) * 24 * 60 * 60) ) / (${date1_year_days_adj:-0} * 24 * 60 * 60) ) ) , $SCL); /** YEARS **/
r( (${monthcount:-0} + ( (${range:-0} - (${fullmonth_days_save:-0} * 24 * 60 * 60) ) / (${date1_month_max_day:-0} * 24 * 60 * 60) ) ) , $SCL); /** MONTHS **/
r( (${range:-0} / ( 7 * 24 * 60 * 60)) , $SCL); /** WEEKS **/
r( (${range:-0} / (24 * 60 * 60)) , $SCL); /** DAYS **/
r( (${range:-0} / (60 * 60)) , $SCL); /** HOURS **/
r( (${range:-0} / 60) , $SCL); /** MINUTES **/")
)
bcy=${bc[0]} bcmo=${bc[1]} bcw=${bc[2]} bcd=${bc[3]} bch=${bc[4]} bcm=${bc[5]}
#ARRAY: 0=YEARS 1=MONTHS 2=WEEKS 3=DAYS 4=HOURS 5=MINUTES
else typeset -F $SCL bcy bcmo bcw bcd bch bcm
bcy="${years_between:-0} + ( (${range:-0} - ( (${daycount_years:-0} + ${daycount_leap_years:-0}) * 24 * 60 * 60.) ) / (${date1_year_days_adj:-0} * 24 * 60 * 60.) )" #YEARS
bcmo="${monthcount:-0} + ( (${range:-0} - (${fullmonth_days_save:-0} * 24 * 60 * 60.) ) / (${date1_month_max_day:-0} * 24 * 60 * 60.) )" #MONTHS
bcw="${range:-0} / ( 7 * 24 * 60 * 60.)" #WEEKS
bcd="${range:-0} / (24 * 60 * 60.)" #DAYS
bch="${range:-0} / (60 * 60.)" #HOURS
bcm="${range:-0} / 60." #MINUTES
fi

#choose layout of single units
if ((OPTT || !OPTLAYOUT))
then #layout one
spcr=' | ' #spacer
prHelpf ${OPTTy:+${bcy}} && range_pr="${bcy} year$SS"
prHelpf ${OPTTmo:+${bcmo}} && range_pr="${range_pr}${range_pr:+$spcr}${bcmo} month$SS"
prHelpf ${OPTTw:+${bcw}} && range_pr="${range_pr}${range_pr:+$spcr}${bcw} week$SS"
prHelpf ${OPTTd:+${bcd}} && range_pr="${range_pr}${range_pr:+$spcr}${bcd} day$SS"
prHelpf ${OPTTh:+${bch}} && range_pr="${range_pr}${range_pr:+$spcr}${bch} hour$SS"
prHelpf ${OPTTm:+${bcm}} && range_pr="${range_pr}${range_pr:+$spcr}${bcm} min$SS"
prHelpf $range ;((!OPTT||OPTTs)) && range_pr="$range_pr${range_pr:+$spcr}$range sec$SS"
((OPTT&&OPTV)) && range_pr="${range_pr%%*([$IFS])}" #bug in ksh93u+ ${var% *}
else #layout two
((n = ${#range}+SCL+1)) #range in seconds is the longest string
prHelpf ${bcy} $n && range_pr=Year$SS$'\t'$SSS${bcy}
prHelpf ${bcmo} $n && range_pr="$range_pr"$'\n'Month$SS$'\t'$SSS${bcmo}
prHelpf ${bcw} $n && range_pr="$range_pr"$'\n'Week$SS$'\t'$SSS${bcw}
prHelpf ${bcd} $n && range_pr="$range_pr"$'\n'Day$SS$'\t'$SSS${bcd}
prHelpf ${bch} $n && range_pr="$range_pr"$'\n'Hour$SS$'\t'$SSS${bch}
prHelpf ${bcm} $n && range_pr="$range_pr"$'\n'Min$SS$'\t'$SSS${bcm}
prHelpf $range $((n - (SCL>0 ? (SCL+1) : 0) ))
range_pr="$range_pr"$'\n'Sec$SS$'\t'$SSS$range
range_pr="${range_pr#*([$IFS])}"
#https://www.themathdoctors.org/should-we-put-zero-before-a-decimal-point/
((OPTLAYOUT>1)) && { p= q=. ;for ((p=0;p<SCL;++p)) ;do q="${q}0" ;done
range_pr="${range_pr// ./0.}" range_pr="${range_pr}${q}" ;}
fi
unset SS SSS
fi

#set printing array with shell results
sh=("$y" "$mo" "$w" "$d" "$h" "$m" "$s")
((y<0||mo<0||w<0||d<0||h<0||m<0||s<0)) && ret=${ret:-1} #negative unit error

# Debugging
if ((DEBUG))
then
#!#
debugf "$@"
fi

#print results
if ((!OPTVERBOSE))
then if [[ ! $date1_iso8601_pr$date1_iso8601 ]]
then date1_iso8601=$(printf \
%04d-%02d-%02dT%02d:%02d:%02d%s%02d:%02d:%02d\\n \
"$yearA" "$monthA" "$dayA" \
"$hourA" "$minA" "$secA" \
"${neg_tzA%1}" "$tzAh" "$tzAm" "$tzAs")
date1_iso8601=${date1_iso8601%%*(:00)}
else date1_iso8601_pr=${date1_iso8601_pr%%*(:00)} #remove excess zeroes
fi
if [[ ! $date2_iso8601_pr$date2_iso8601 ]]
then date2_iso8601=$(printf \
%04d-%02d-%02dT%02d:%02d:%02d%s%02d:%02d:%02d\\n \
"$yearB" "$monthB" "$dayB" \
"$hourB" "$minB" "$secB" \
"${neg_tzB%1}" "$tzBh" "$tzBm" "$tzBs")
date2_iso8601=${date2_iso8601%%*(:00)}
else date2_iso8601_pr=${date2_iso8601_pr%%*(:00)}
fi

printf '%s%s\n%s%s%s\n%s%s%s\n%s\n' \
DATES "${OPTDD+#}${badges}${neg_range%1}" \
"${date1_iso8601_pr:-${date1_iso8601:-$inputA}}" ''${unix1:+$'\t'} "$unix1" \
"${date2_iso8601_pr:-${date2_iso8601:-$inputB}}" ''${unix2:+$'\t'} "$unix2" \
RANGES
fi
prfmt='%dY %02dM %02dW %02dD %02dh %02dm %02ds' #print format for the compound range
((OPTVERBOSE>3)) && prfmt='%dY%02dM%02dW%02dD%02dh%02dm%02ds' #AST `date -E' style
((OPTVERBOSE<2 || OPTVERBOSE>2)) && printf "${prfmt}\n" "${sh[@]}"
((OPTVERBOSE<3)) && printf '%s\n' "${range_pr:-$range secs}"

return ${ret:-0}
}

#execute result checks against `datediff' and `date'
#check manually in case of divergence as this function is overloaded
#beware of opt -R and unset $TZ and offsets (we defaults to UTC while `date' may set random offsets)
function debugf
{
unset iA iB tA tB dd ddout y_dd mo_dd w_dd d_dd h_dd m_dd s_dd range_check unix1t unix2t checkA_pr checkB_pr checkA_pr_dow checkB_pr_dow checkA_utc checkB_utc date_cmd_save TZ_save brk
date_cmd_save="${DATE_CMD}" DATE_CMD=date TZ_save=$TZ TZ=UTC${TZ##*$GLOBUTC}

[[ $2 = *[Tt:]*[+-]$GLOBTZ && $1 = *[Tt:]*[+-]$GLOBTZ ]] || echo warning: input dates are missing offset/tz bits! >&2
iB="${2:-${inputB}}" iA="${1:-${inputA}}"
iB="${iB:0:25}" iA="${iA:0:25}"
((${#iB}==10)) && iB=${iB}T00:00:00
((${#iA}==10)) && iA=${iA}T00:00:00
((${#iB}==19)) && iB="${iB}+00:00"
((${#iA}==19)) && iA="${iA}+00:00"
iB=${iB/-00:00/+00:00} iA=${iA/-00:00/+00:00}

#utc time strings
tB=$(printf \
%04d-%02d-%02dT%02d:%02d:%02d%s%02d:%02d\\n \
"$yearB" "$monthB" "$dayB" \
"$hourB" "$minB" "$secB" \
"${neg_tzB%1}" $tzBh $tzBm)
tA=$(printf \
%04d-%02d-%02dT%02d:%02d:%02d%s%02d:%02d\\n \
"$yearA" "$monthA" "$dayA" \
"$hourA" "$minA" "$secA" \
"${neg_tzA%1}" $tzAh $tzAm)
tB=${tB:0:25} tA=${tA:0:25}
tB=${tB/-00:00/+00:00} tA=${tA/-00:00/+00:00}

if [[ $date_cmd_save = false ]]
then
if ((TZs)) || [[ $TZ = *:*:*:* ]] || [[ $tzA = *:*:*:* ]] || [[ $tzB = *:*:*:* ]]
then echo "warning: \`datediff' and \`date' may not take offsets with seconds" >&2
((ret+=230))
fi

if ((TZh||TZm))
then checkB_pr=$(datefun -Iseconds $iB)
checkA_pr=$(datefun -Iseconds $iA)
else checkB_pr=$date2_iso8601_pr checkA_pr=$date1_iso8601_pr
fi
if ((OPTRR))
then checkB_pr_dow=$(datefun "$iB")
checkA_pr_dow=$(datefun "$iA")
fi

checkB_utc=$(TZ=UTC datefun -Iseconds $iB)
checkA_utc=$(TZ=UTC datefun -Iseconds $iA)
#`date' iso offset must not exceed minute precision [+-]XX:XX !

#check generated unix times against `date'
unix2t=$(datefun "$iB" +%s)
unix1t=$(datefun "$iA" +%s)
range_check=$((unix2t-unix1t))
fi
if ((OPTRR))
then checkB_pr_dow="${checkB_pr_dow:-$date2_iso8601_pr}"
checkA_pr_dow="${checkA_pr_dow:-$date1_iso8601_pr}"
fi

#compound range check against `datediff'
#`datediff' offset range is between -14h and +14h!
ddout=$(datediff -f'%Y %m %w %d %H %M %S' "$tA" "$tB") || ((ret+=250))
read y_dd mo_dd w_dd d_dd h_dd m_dd s_dd <<<"$ddout"
dd=(${y_dd#-} $mo_dd $w_dd $d_dd $h_dd $m_dd $s_dd)

DATE_CMD="$date_cmd_save" TZ=$TZ_save
{
{
{ [[ ${date2_iso8601_pr:0:25} = $checkB_pr ]] &&
[[ ${date1_iso8601_pr:0:25} = $checkA_pr ]] ;} ||
{ [[ ${date2_iso8601_pr:0:3} = ${checkB_pr_dow:0:3} ]] &&
[[ ${date1_iso8601_pr:0:3} = ${checkA_pr_dow:0:3} ]] ;}
} &&

[[ $tB = ${checkB_utc:-$tB} ]] &&
[[ $tA = ${checkA_utc:-$tA} ]] &&

[[ $unix1 = ${unix1t:-$unix1} && $unix2 = ${unix2t:-$unix2} ]] &&
[[ $range = "${range_check:-$range}" ]] &&

[[ ${sh[*]} = "${dd[*]:-${sh[*]}}" ]]
} || { #brk='\n'
echo -ne "\033[2K" >&2
echo ${brk+-e} "\
sh=${sh[*]} dd=${dd[*]} | $brk"\
"$iA $iB | $brk"\
"${range:-unavail} ${range_check:-unavail} | $brk"\
"${date1_iso8601_pr:0:25} $checkA_pr | $brk"\
"${date2_iso8601_pr:0:25} $checkB_pr | $brk"\
"${date1_iso8601_pr:0:3} ${checkA_pr_dow:0:3} | $brk"\
"${date2_iso8601_pr:0:3} ${checkB_pr_dow:0:3} | $brk"\
"$tB $checkB_utc | $brk"\
"$tA $checkA_utc | $brk"\
"$unix1 $unix1t | $brk"\
"$unix2 $unix2t | $brk"\
"${date_cmd_save%date}"

((ret+=1))
}

#((DEBUG>1)) && return ${ret:-0} #!#
((DEBUG>1)) && exit ${ret:-0} #!#
return 0
}


## Parse options
while getopts 01234567890DdeFf:hlmRr@tuv opt
do case $opt in
[0-9]) SCL="$SCL$opt"
;;
d) ((++DEBUG))
;;
D) [[ ${DATE_CMD} = false ]] && OPTDD=1 ;DATE_CMD=false
;;
e) OPTE=1 OPTL=
;;
F) ((++OPTFF))
;;
f) INPUT_FMT="$OPTARG" OPTF=1 #input format string for `BSD date'
;;
h) while read
do [[ "$REPLY" = \#\ v* ]] && echo "$REPLY $SHELL" && break
done <"$0"
echo "$HELP" ;exit
;;
l) OPTL=1 OPTE=
;;
m) OPTM=1
;;
R) OPTRR=1
;;
r|@) OPTR=1
;;
t) ((++OPTLAYOUT))
;;
u) OPTU=1
;;
v) ((++OPTVERBOSE, ++OPTV))
;;
\?) exit 1
;;
esac
done
shift $((OPTIND -1)); unset opt

#set proper environment!
SCL="${SCL:-1}" #scale defaults
((OPTU)) && TZ=UTC #set UTC time zone
export TZ

#test for BSD or GNU date for datefun()
[[ ${DATE_CMD} ]] ||
if DATE_CMD=date;
! ${DATE_CMD} --version
then if gdate --version
then DATE_CMD=gdate
elif command -v date
then BSDDATE=1
else DATE_CMD=false
fi
fi >/dev/null 2>&1

#stdin input (skip it for option -F)
[[ ${1//[$IFS]}$OPTFF = $GLOBOPT ]] && opt="$1" && shift
if ((!($#+OPTFF) )) && [[ ! -t 0 ]]
then
globtest="*([$IFS])@($GLOBDATE?(+([$SEP])$GLOBTIME)|$GLOBTIME)*([$IFS])@($GLOBDATE?(+([$SEP])$GLOBTIME)|$GLOBTIME)?(+([$IFS])$GLOBOPT)*([$IFS])" #glob for two ISO8601 dates and possibly pos arg option for single unit range
while IFS= read -r || [[ $REPLY ]]
do ar=($REPLY) ;((${#ar[@]})) || continue
if ((!$#))
then set -- "$REPLY" ;((OPTL)) && break
#check if arg contains TWO ISO8601 dates and break
if ((${#ar[@]}==3||${#ar[@]}==2)) && [[ \ $REPLY = @(*[$IFS]$GLOBOPT*|$globtest) ]]
then set -- $REPLY ;[[ $1 = $GLOBOPT ]] || break
fi
else if ((${#ar[@]}==2)) && [[ \ $REPLY = @(*[$IFS]$GLOBOPT|$globtest) ]]
then set -- "$@" $REPLY
else set -- "$@" "$REPLY"
fi ;break
fi
done ;unset ar globtest REPLY
[[ ${1//[$IFS]} = $GLOBOPT ]] && opt="$1" && shift
fi
[[ $opt ]] && set -- "$@" "$opt"

#set single time unit
opt="${opt:-${@: -1}}" opt="${opt//[$IFS]}"
if [[ $opt$OPTFF = $GLOBOPT ]]
then OPTT=1 OPTVERBOSE=2 OPTLAYOUT=
case $opt in
[yY]) OPTTy=1;;
[mM][oO]) OPTTmo=1;;
[wW]) OPTTw=1;;
[dD]) OPTTd=1;;
[hH]) OPTTh=1;;
[mM]) OPTTm=1;;
[sS]) OPTTs=1;;
esac ;set -- "${@:1:$#-1}"
else OPTTy=1 OPTTmo=1 OPTTw=1 OPTTd=1 OPTTh=1 OPTTm=1 OPTTs=1
fi ;unset opt
#caveat: `gnu date' understands `-d[a-z]', do `-d[a-z]0' to pass.

#whitespace trimming
if (($#>1))
then set -- "${1#"${1%%[!$IFS]*}"}" "${2#"${2%%[!$IFS]*}"}" "${@:3}"
set -- "${1%"${1##*[!$IFS]}"}" "${2%"${2##*[!$IFS]}"}" "${@:3}"
elif (($#))
then set -- "${1#"${1%%[!$IFS]*}"}" ;set -- "${1%"${1##*[!$IFS]}"}"
fi

if ((OPTL))
then for YEAR
do is_year "$YEAR" || continue
if ! is_leapyear_verbose "$YEAR"
then (($?>1)) && RET=2 ;RET="${RET:-$?}"
fi
done ;exit $RET
elif ((OPTE))
then for YEAR
do is_year "$YEAR" || continue
DATE=$(easterf "$YEAR") ;echo $DATE
done
elif ((OPTM))
then for DATE_Y #fill in months and days
do if [[ $DATE_Y = +([0-9]) ]]
then set -- ;OPTM=2
for ((M=1;M<=12;++M)) ;do set -- "$@" "${DATE_Y}-$M" ;done
else set -- "$DATE_Y" ;PHASE_SKIP=
fi
for DATE_M
do if [[ $DATE_M = +([0-9])[$SEP]+([0-9]) ]]
then set -- ;OPTM=2
DMAX=$(month_maxday "${DATE_M#*[$SEP]}" "${DATE_M%[$SEP]*}")
for ((D=1;D<=DMAX;++D)) ;do set -- "$@" "${DATE_M}-$D" ;done
else set -- "$DATE_M" ;PHASE_SKIP=
fi
for DATE
do set -- ${DATE//[$SEP]/ } #input is ISO8601
phase_of_the_moon "$3" "$2" "$1"
done
done
done
elif ((OPTFF))
then friday_13th "$@"
else
#-r, unix times
if ((OPTR && $#>1))
then set -- @"${1#@}" @"${2#@}" "${@:3}"
elif ((OPTR && $#))
then set -- @"${1#@}"
fi
mainf "$@"
fi


Click here to read the complete article
Re: Datediff script

<tlambo$37ur7$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6854&group=comp.unix.shell#6854

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: janis_papanagnou+ng@hotmail.com (Janis Papanagnou)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sat, 19 Nov 2022 14:40:08 +0100
Organization: A noiseless patient Spider
Lines: 38
Message-ID: <tlambo$37ur7$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tlaivk$37mt8$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
Injection-Date: Sat, 19 Nov 2022 13:40:08 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="b71fd446f419096a1018c48ad7ed0857";
logging-data="3406695"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1+5P6zWEV73BUXZ2KxdNU0C"
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101
Thunderbird/45.8.0
Cancel-Lock: sha1:EnqM492cg5R4Q6Pr0Wi50QS4O04=
X-Enigmail-Draft-Status: N1110
In-Reply-To: <tlaivk$37mt8$1@dont-email.me>
 by: Janis Papanagnou - Sat, 19 Nov 2022 13:40 UTC

On 19.11.2022 13:42, castAway wrote:
> Ksh93 `superior'
> parameter scoping in functions was a litte hard to deal with [...]

Note that ksh's 'typeset' specifies variables' meta-attributes.
The local scope in functions is just one, so it's not comparable
with bash's simple 'local' keyword. Any ksh's 'typeset' is also
not that portable. As mentioned upthread there's also the more
portable f()(...) instead of f(){...;} if all you want is
local-scoped variables.

> [...] Specially, the new friday_13th() function
> may still need some more work.

Is there anything more about that function than just checking the
day-of-week (Friday) and date-in-month (13)?

> I reckon it would be cool to have a shell function to convert UNIX times to
> ISO-8601 (and RFC-5322) formats.

You mean to convert "seconds since 'Unix Epoch'" to e.g. ISO time?
A lot of things can be dome with ksh's built-in printf function and
its "%(...)T" specifier. You can resort to GNU date or GNU awk for
other time functions, e.g.

$ awk -v s=1668864108 'BEGIN{print strftime("%FT%T",s)}'
2022-11-19T14:21:48

but I'm not sure about your portability requirements and GNU tools
might not be available.

I'm also still unsure about the supported date ranges. I a post
quite some time ago I posted some observations with the different
ranges of time functions in 'date', 'ksh', and 'awk'; all we seems
to be able to rely on was (IIRC) the rather short Unix-Epoch range.

Janis

Re: Datediff script

<jtsegcFqnnaU1@mid.individual.net>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6855&group=comp.unix.shell#6855

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!news.swapon.de!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail
From: jpstewart@personalprojects.net (John-Paul Stewart)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sat, 19 Nov 2022 11:27:25 -0500
Lines: 15
Message-ID: <jtsegcFqnnaU1@mid.individual.net>
References: <tkju7j$54v$1@gioia.aioe.org> <tkk621$l9ma$1@dont-email.me>
<tkkfse$1e9d$1@gioia.aioe.org> <tkltmu$sve7$1@dont-email.me>
<tl1dhb$25vcc$2@dont-email.me>
Mime-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Trace: individual.net 3Tpty/f3/OZZP1OQFnLrcQUkDvjT1uPGjnSlAQtICd9DDekZre
Cancel-Lock: sha1:ZfYfIGvtIO3aOEGO/L7f9QmW+xk=
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
Thunderbird/102.4.1
Content-Language: en-CA
In-Reply-To: <tl1dhb$25vcc$2@dont-email.me>
 by: John-Paul Stewart - Sat, 19 Nov 2022 16:27 UTC

On 2022-11-15 20:14, castAway wrote:
> On 11/11/22 13:36, Janis Papanagnou wrote:
>
>>    function phase_of_the_moon (now)    // 0-7, with 0: new, 4: full
>
> Bash integer arithmetics do floor rounding by deafults, don't trust,
> check it, thus $(( 7/4 )) returns 1... Like Ksh, so maybe the floor rounding
> function you mentioned is an overhead... Maybe in Java scripting all integers
> are calculated as floating point?

Yes, in JavaScript all numbers are indeed floating point. Well, unless
you explicitly use the BigInt type:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Numbers_and_dates#numbers

Re: Datediff script

<tlb0o4$38op5$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6856&group=comp.unix.shell#6856

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: no@where.com (castAway)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sat, 19 Nov 2022 13:37:23 -0300
Organization: A noiseless patient Spider
Lines: 56
Message-ID: <tlb0o4$38op5$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tlaivk$37mt8$1@dont-email.me>
<tlambo$37ur7$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Injection-Date: Sat, 19 Nov 2022 16:37:24 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="c8e09f965d0c49b79a6c694f0134c3d4";
logging-data="3433253"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX19hbmIIAYxDF0jH2/zKHMCG+LTbxyvEDoI="
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
Thunderbird/102.4.1
Cancel-Lock: sha1:7Jk6Kb/dVfnMYWMQifS81T9y9MI=
Content-Language: pt-BR, en-GB
In-Reply-To: <tlambo$37ur7$1@dont-email.me>
 by: castAway - Sat, 19 Nov 2022 16:37 UTC

On 11/19/22 10:40, Janis Papanagnou wrote:
> As mentioned upthread there's also the more
> portable f()(...) instead of f(){...;} if all you want is
> local-scoped variables.

I had missed that subtlety in the thread. The f() (...) syntax may
make the script run slower but maxes out compatibility, that is very
useful to learn!

>
> I'm also still unsure about the supported date ranges. I a post
> quite some time ago I posted some observations with the different
> ranges of time functions in 'date', 'ksh', and 'awk'; all we seems
> to be able to rely on was (IIRC) the rather short Unix-Epoch range.

Indeed, I remember having read the thread `Range of dates' (15 Jan 2021)
after a search on Usenet at the start of this year about ranges of
times. My datediff.sh script does not rely on `date' because I had
seen some narrow calendrical limits of `date' depending on the system
(`date' run under Termux has narrower time ranges, if I remember
correctly). The script follows the proleptic Gregorian calendar,
as already said, and there is just one adjustment I could think of
to get Julian dates that is skipping year 0000, but that would differ
from `date' command logic.

% date -u -d 0000-01-01 +%s
-62167219200

% datediff.sh -- -0001-01-01 -0000-01-01
DATES##
-001-01-01T00:00:00+00 -62198755200
0000-01-01T00:00:00+00 -62167219200
RANGES
1Y 00M 00W 00D 00h 00m 00s
1.0 year | 12.0 months | 52.1 weeks | 365.0 days | 8760.0 hours | 525600.0 mins | 31536000 secs

In the example above, `date' will be warped to process input dates
into UNIX times. However, GNU date will fail and the script will,
first, calculate the time elapsed between both dates, and then calculate
the time interval/elapsed from the closest year to UNIX epoch zero
time. That way, that is possible to generate UINX times with
arithmetics, as we count all days between these dates to get the
time in seconds.

> Is there anything more about that function than just checking the
> day-of-week (Friday) and date-in-month (13)?

You can also set positional arguments to get the combination of
day-of-week and date-in-month, for example:

% datediff.sh -F 1 mon
Mon, 01 May 2023 is 163 days ahead

Cheers,
JSN

Re: Datediff script

<tlb27f$38rr9$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6857&group=comp.unix.shell#6857

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: no@where.com (castAway)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sat, 19 Nov 2022 14:02:39 -0300
Organization: A noiseless patient Spider
Lines: 21
Message-ID: <tlb27f$38rr9$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tlaivk$37mt8$1@dont-email.me>
<tlambo$37ur7$1@dont-email.me> <tlb0o4$38op5$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Injection-Date: Sat, 19 Nov 2022 17:02:39 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="c8e09f965d0c49b79a6c694f0134c3d4";
logging-data="3436393"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1+lf5Yg6aTlp4R4Z7SARwgHK6o6SvM3Ip8="
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
Thunderbird/102.4.1
Cancel-Lock: sha1:AQKvDYzo3aG/ljgceFVTmBibrJQ=
Content-Language: pt-BR, en-GB
In-Reply-To: <tlb0o4$38op5$1@dont-email.me>
 by: castAway - Sat, 19 Nov 2022 17:02 UTC

On 11/19/22 13:37, castAway wrote:

Sorry, in the previous example, `date' will fail but shell
time function is still used to get current time. For the
manual UNIX time generation facility to work, the running
shell must not support built-in time functions (must be
earlier than bash 4.2, for example). To force manual UNIX
time generation in the script, set flags -DD to disable
`date' and shell time built-ins wrapping.

% datediff.sh -DD -- -0001-01-01 -0000-01-01
DATES##
-001-01-01T00:00:00+00 -62198755200
0000-01-01T00:00:00+00 -62167219200
RANGES
1Y 00M 00W 00D 00h 00m 00s
1.0 year | 12.0 months | 52.1 weeks | 365.0 days | 8760.0 hours | 525600.0 mins | 31536000 secs

JSN

Re: Datediff script

<tlb2eq$38t5l$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6858&group=comp.unix.shell#6858

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: no@where.com (castAway)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sat, 19 Nov 2022 14:06:34 -0300
Organization: A noiseless patient Spider
Lines: 6
Message-ID: <tlb2eq$38t5l$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tlaivk$37mt8$1@dont-email.me>
<tlambo$37ur7$1@dont-email.me> <tlb0o4$38op5$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Injection-Date: Sat, 19 Nov 2022 17:06:34 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="c8e09f965d0c49b79a6c694f0134c3d4";
logging-data="3437749"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX18m5Vkzo1cbf7hqFlqcyuDxNbylRggjGRE="
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
Thunderbird/102.4.1
Cancel-Lock: sha1:ZcTGYcn1qdpr7qSZYZUGKKFy/1Q=
Content-Language: pt-BR, en-GB
In-Reply-To: <tlb0o4$38op5$1@dont-email.me>
 by: castAway - Sat, 19 Nov 2022 17:06 UTC

No, in the previous examples, both will require manual
UNIX time generation. Shell time facility is only used to
print RFC timestamps, and get current time if any date1
or date2 is not set by user input. So in those previous
e-mail examples I just sent, it does not matter if using
flag -DD or not.

Re: Datediff script

<tlb9ao$39fvp$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6859&group=comp.unix.shell#6859

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: janis_papanagnou+ng@hotmail.com (Janis Papanagnou)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sat, 19 Nov 2022 20:03:52 +0100
Organization: A noiseless patient Spider
Lines: 39
Message-ID: <tlb9ao$39fvp$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tlaivk$37mt8$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
Injection-Date: Sat, 19 Nov 2022 19:03:52 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="b71fd446f419096a1018c48ad7ed0857";
logging-data="3457017"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1/LVKgUBM6G5b3Nd3qAg4be"
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101
Thunderbird/45.8.0
Cancel-Lock: sha1:+6kld6Lp+aLkzQFOD10Ht1XDHbE=
In-Reply-To: <tlaivk$37mt8$1@dont-email.me>
X-Enigmail-Draft-Status: N1110
 by: Janis Papanagnou - Sat, 19 Nov 2022 19:03 UTC

On 19.11.2022 13:42, castAway wrote:
> I am afraid to have gone a little over the top. The lunar phase function
> was implemented, [...]

> #return phase of the moon, use UTC time
> #usage: phase_of_the_moon year [month] [day]
> function phase_of_the_moon #0-7, with 0: new, 4: full
> {
> typeset day month year diy goldn epact
> day="${1#0}" month="${2#0}" year="${3##+(0)}"
> day=${day:-1} month=${month:-1} year=${year:-0}
> diy=$(get_day_in_year "$day" "$month" "$year")
> ((goldn = (year % 19) + 1))
> ((epact = (11 * goldn + 18) % 30))
> (((epact == 25 && goldn > 11) || epact == 24 )) && ((epact++))
> case $(( ( ( ( ( (diy + epact) * 6) + 11) % 177) / 22) &
> 7)) in
> 0) set -- 'New Moon' ;; #.0
> 1) set -- 'Waxing Crescent' ;;
> 2) set -- 'First Quarter' ;; #.25
> 3) set -- 'Waxing Gibbous' ;;
> 4) set -- 'Full Moon' ;; #.5
> 5) set -- 'Waning Gibbous' ;;
> 6) set -- 'Last Quarter' ;; #.75
> 7) set -- 'Waning Crescent' ;;
> esac

One thing I forgot that I wanted to point out...
We should be aware that the 8-value quantization will result in phases
of 3 or 4 consecutive days with the same moon phase. I noticed that in
the game of Nethack (where that code stems from) the "Nethack new moon"
phase _starts_ at the day when _real_ new moon actually is. That might
not be what one expects, though. At least my expectation was that it
would be better to either center the real moon phase date around these
3-4 days phase, or give up the quantization and calculate it on a 29.5
days per month basis.

Janis

Re: Datediff script

<tlbqsb$3aqsd$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6860&group=comp.unix.shell#6860

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: no@where.com (castAway)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sat, 19 Nov 2022 21:03:22 -0300
Organization: A noiseless patient Spider
Lines: 65
Message-ID: <tlbqsb$3aqsd$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tlaivk$37mt8$1@dont-email.me>
<tlambo$37ur7$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Injection-Date: Sun, 20 Nov 2022 00:03:23 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="5d5cda34b45fca886cb781fc218be21d";
logging-data="3500941"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1+xtIyAOAG52iQt1qEjad9yPlDRYyfNh5M="
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
Thunderbird/102.4.1
Cancel-Lock: sha1:AP4+ftHggkFCEie5O9ln7eD4/r0=
Content-Language: pt-BR, en-GB
In-Reply-To: <tlambo$37ur7$1@dont-email.me>
 by: castAway - Sun, 20 Nov 2022 00:03 UTC

On 11/19/22 10:40, Janis Papanagnou wrote:
> I'm also still unsure about the supported date ranges. I a post
> quite some time ago I posted some observations with the different
> ranges of time functions in 'date', 'ksh', and 'awk'; all we seems
> to be able to rely on was (IIRC) the rather short Unix-Epoch range.
I am mulling over about shell arithmetics limits.

As the script counts all days between dates and then sums up it all as
seconds before doing divisions to get single unit time intervals, such
as 60.5 years, the FP result incurs in overflow at about 68. years worth
of time.

Some testing reveals that my script can only count up to 2147483647 seconds
(about 68 years), for the FP results of single units of time. That number
is one decrement below 2^32 / 2 = 4294967296 / 2 = 2147483648, because
there is no bits left for a double point after that...

Spurious negative results should exit with 1, but I just check the compound
time range, for negative results (which should not happen!).

That is one of the reasons I decided to use Bc to calculate those FPs,
in retrospective.

The compound time interval, and the UNIX time generation facility of
the script, though, should work for much larger amounts of time.

Those functions work with shell integer arithmetics wich have got a limit
of 19 digits. It should work with time intervals up to close to
9,223,372,036,854,775,807 seconds, which is about 292 billions years!

And, if we use Bc, we can get FP single unit results within that limit
as well... I will revert to using Bc for single unit time ranges by
defaults and add a note on a LIMITS section of the help page!

A Bc script could calculate within much broader FP and integer limits.

### MaxScale - Get the maximum scale from your GNU BC
### BC is currently only 32bit in all cases.
### The package manager recognizes that it might have to install
### 32bit libraries for a 64 bit environment, but continue to be
### 32 bits libraries.
### That is why your maximum scale is always 2147483647 (though
### is safer to use 2147483647, because by reason of the minus or
### the decimal symbol in case of negative or decimal operations
### by reason of the minus and decimal symbol)
### 1111111111111111111111111111110 bits.
### If you try to add just 1 bit more, you will get a fatal error
### 'Out of memory for malloc'.
### Which explain that more memory can't be allocated, even if there
### is not an overflow because you are running a >32 bits computer.
### If you want to use bc in bash
### (such as $(echo "scale=2147483646;1/6" | bc) | tee -a division)
### you will get 'bash: xrealloc: cannot allocate 18446744071562067968
### bytes;'
### (2^31 digits represent A LOT of bytes for a normal 32/64 bits
### computer memory!) and 'Out of memory malloc' in case you want
### to assign the value to a variable or simply copy and paste
### outside bc. However, you can still operate with such a large
### number inside bc without problems while you don't exceed 2^31
### digits. Isn't that awesome?"
#scale=2147483646

Cheers,
JSN

Re: Datediff script

<tlc2f1$3be6l$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6861&group=comp.unix.shell#6861

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: no@where.com (castAway)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sat, 19 Nov 2022 23:12:48 -0300
Organization: A noiseless patient Spider
Lines: 9
Message-ID: <tlc2f1$3be6l$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tlaivk$37mt8$1@dont-email.me>
<tlambo$37ur7$1@dont-email.me> <tlbqsb$3aqsd$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Injection-Date: Sun, 20 Nov 2022 02:12:49 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="5d5cda34b45fca886cb781fc218be21d";
logging-data="3520725"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX189wtRltSD/V8KzKOSJpOHJeypUhZy3A9A="
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
Thunderbird/102.4.1
Cancel-Lock: sha1:FinLlK30xV786vhONHfwkcTlrh0=
In-Reply-To: <tlbqsb$3aqsd$1@dont-email.me>
Content-Language: pt-BR, en-GB
 by: castAway - Sun, 20 Nov 2022 02:12 UTC

On 11/19/22 21:03, castAway wrote:
> On 11/19/22 10:40, Janis Papanagnou wrote:
>> [...]

Well, I was testing the script under Termux in my Android mobile phone.
shell arithmetics seem to perform much better (i.e. larger limits for FP)
under Linux 64bit i7 cpu.

JSN

Re: Datediff script

<tlctnl$3g5ff$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6862&group=comp.unix.shell#6862

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: no@where.com (castAway)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sun, 20 Nov 2022 06:58:13 -0300
Organization: A noiseless patient Spider
Lines: 16
Message-ID: <tlctnl$3g5ff$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tlaivk$37mt8$1@dont-email.me>
<tlb9ao$39fvp$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Injection-Date: Sun, 20 Nov 2022 09:58:13 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="5d5cda34b45fca886cb781fc218be21d";
logging-data="3675631"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1/Gae6NRYGhqu8WYnMR2tQjyLjDR/XyO5c="
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
Thunderbird/102.4.1
Cancel-Lock: sha1:xmKo1ezMsJdIPjszS0eg395+yqQ=
Content-Language: pt-BR, en-GB
In-Reply-To: <tlb9ao$39fvp$1@dont-email.me>
 by: castAway - Sun, 20 Nov 2022 09:58 UTC

On 11/19/22 16:03, Janis Papanagnou wrote:
> One thing I forgot that I wanted to point out...
> We should be aware that the 8-value quantization will result in phases
> of 3 or 4 consecutive days with the same moon phase. I noticed that in
> the game of Nethack (where that code stems from) the "Nethack new moon"
> phase _starts_ at the day when _real_ new moon actually is. That might
> not be what one expects, though. At least my expectation was that it
> would be better to either center the real moon phase date around these
> 3-4 days phase, or give up the quantization and calculate it on a 29.5
> days per month basis.
>

I think the moon phase is a little subjective as brightness of the moon
varies from the observed GPS location in the globe.

Re: Datediff script

<tlddmm$3hfla$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=6863&group=comp.unix.shell#6863

  copy link   Newsgroups: comp.unix.shell
Path: i2pn2.org!i2pn.org!eternal-september.org!reader01.eternal-september.org!.POSTED!not-for-mail
From: janis_papanagnou+ng@hotmail.com (Janis Papanagnou)
Newsgroups: comp.unix.shell
Subject: Re: Datediff script
Date: Sun, 20 Nov 2022 15:30:46 +0100
Organization: A noiseless patient Spider
Lines: 31
Message-ID: <tlddmm$3hfla$1@dont-email.me>
References: <tkju7j$54v$1@gioia.aioe.org> <tlaivk$37mt8$1@dont-email.me>
<tlb9ao$39fvp$1@dont-email.me> <tlctnl$3g5ff$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
Injection-Date: Sun, 20 Nov 2022 14:30:46 -0000 (UTC)
Injection-Info: reader01.eternal-september.org; posting-host="014f2acb96c92a68192a0429793297f8";
logging-data="3718826"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1+PFs9BzkYY7v9W/vZn98rv"
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101
Thunderbird/45.8.0
Cancel-Lock: sha1:YHn93SNsYS7d6DhBWmB5QKRnaB0=
X-Enigmail-Draft-Status: N1110
In-Reply-To: <tlctnl$3g5ff$1@dont-email.me>
 by: Janis Papanagnou - Sun, 20 Nov 2022 14:30 UTC

On 20.11.2022 10:58, castAway wrote:
>
> I think the moon phase is a little subjective as brightness of the moon
> varies from the observed GPS location in the globe.

The moon phase is primarily depending the position of moon and sun as
seen from earth; if (for example) moon and sun are in an orthogonal
angle you have a half moon phase. Because of the magnitude of actual
distances and diameters ([avg.] sun ~150'000'000 km, moon ~184'000 km,
earth diameter ~12'700 km) the concrete observation position on earth
is not significant.

S M
E

The very rough 8-value discretization of moon phases that I mentioned
that lead to phases of 3-4 days(!) is quite significant. And the other
consequence - what is a sensible definition - is even more important;

|---------phase---------|
day 1 day 2 day 3 day 4
+-----+-----+-----+-----+
^ ^
a b

whether, say, new moon is defined as starting the quantized phase (a)
or being defined as the mid of the phase (b) means a difference in
accuracy of 1.5 or 2.0 days.

Janis

Pages:12
server_pubkey.txt

rocksolid light 0.9.81
clearnet tor