add usage example
[shellsnippets/shellsnippets.git] / mksh / timefns
1 # From $MirOS: www/mk/common,v 1.11 2018/08/29 02:54:14 tg Exp $
2 #-
3 # Copyright © 2007, 2008, 2012, 2013, 2014, 2018
4 #       Thorsten “mirabilos” Glaser <tg@mirbsd.org>
5 #
6 # Provided that these terms and disclaimer and all copyright notices
7 # are retained or reproduced in an accompanying document, permission
8 # is granted to deal in this work without restriction, including un‐
9 # limited rights to use, publicly perform, distribute, sell, modify,
10 # merge, give away, or sublicence.
11 #
12 # This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
13 # the utmost extent permitted by applicable law, neither express nor
14 # implied; without malicious intent or gross negligence. In no event
15 # may a licensor, author or contributor be held liable for indirect,
16 # direct, other damage, loss, or other issues arising in any way out
17 # of dealing in the work, even if advised of the possibility of such
18 # damage or existence of a defect, except proven that it results out
19 # of said person’s immediate fault when using the work as intended.
20 #-
21 # Time manipulation functions in Pure mksh™ – POSIX, no leap seconds
22 #
23 # Example use by the MirOS website: https://www.mirbsd.org/cvs.cgi/www/mk/
24
25 # magic from MirOS: src/kern/c/mirtime.c,v 1.3 2011/11/20 23:40:10 tg Exp $
26
27 # struct tm members and (POSIX) time functions
28 typeset -ir tm_sec=0            # seconds [0-59]
29 typeset -ir tm_min=1            # minutes [0-59]
30 typeset -ir tm_hour=2           # hours [0-23]
31 typeset -ir tm_mday=3           # day of month [1-31]
32 typeset -ir tm_mon=4            # month of year - 1 [0-11]
33 typeset -ir tm_year=5           # year - 1900
34 typeset -ir tm_wday=6           # day of week [0 = sunday]      input:ignored
35 typeset -ir tm_yday=7           # day of year [0-365]           input:ignored
36 typeset -ir tm_isdst=8          # summer time act.? [0/1] (0)   input:ignored
37 typeset -ir tm_gmtoff=9         # seconds offset from UTC (0)
38 typeset -ir tm_zone=10          # abbrev. of timezone ("UTC")   input:ignored
39
40 set -A mirtime_months -- Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
41 set -A mirtime_wdays -- Sun Mon Tue Wed Thu Fri Sat
42 readonly mirtime_months[*] mirtime_wdays[*]
43
44 # $ timet2mjd posix_timet
45 # ⇒ mjd sec
46 function timet2mjd {
47         local -i10 mjd=$1 sec
48
49         (( sec = mjd % 86400 ))
50         (( mjd = (mjd / 86400) + 40587 ))
51         while (( sec < 0 )); do
52                 (( --mjd ))
53                 (( sec += 86400 ))
54         done
55
56         print -- $mjd $sec
57 }
58
59 # $ mjd2timet mjd sec
60 # ⇒ posix_timet
61 function mjd2timet {
62         local -i10 t=$1 sec=$2
63
64         (( t = (t - 40587) * 86400 + sec ))
65         print -- $t
66 }
67
68 # $ mjd_explode mjd sec
69 # ⇒ tm_sec tm_min tm_hour tm_mday tm_mon tm_year \
70 #   tm_wday tm_yday "0" "0" "UTC"
71 function mjd_explode {
72         local tm
73         set -A tm
74         local -i10 sec=$2 day yday mon year=$1
75
76         while (( sec < 0 )); do
77                 (( --year ))
78                 (( sec += 86400 ))
79         done
80         while (( sec >= 86400 )); do
81                 (( ++year ))
82                 (( sec -= 86400 ))
83         done
84
85         (( day = year % 146097 + 678881 ))
86         (( year = 4 * ((year / 146097) + (day / 146097)) ))
87         (( day %= 146097 ))
88         (( tm[tm_wday] = (day + 3) % 7 ))
89         if (( day == 146096 )); then
90                 (( year += 3 ))
91                 (( day = 36524 ))
92         else
93                 (( year += day / 36524 ))
94                 (( day %= 36524 ))
95         fi
96         (( year = 4 * ((year * 25) + (day / 1461)) ))
97         (( day %= 1461 ))
98         (( yday = (day < 306) ? 1 : 0 ))
99         if (( day == 1460 )); then
100                 (( year += 3 ))
101                 (( day = 365 ))
102         else
103                 (( year += day / 365 ))
104                 (( day %= 365 ))
105         fi
106         (( yday += day ))
107         (( day *= 10 ))
108         (( mon = (day + 5) / 306 ))
109         (( day = ((day + 5) % 306) / 10 ))
110         if (( mon >= 10 )); then
111                 (( mon -= 10 ))
112                 (( yday -= 306 ))
113                 (( ++year ))
114         else
115                 (( mon += 2 ))
116                 (( yday += 59 ))
117         fi
118         (( tm[tm_sec] = sec % 60 ))
119         (( sec /= 60 ))
120         (( tm[tm_min] = sec % 60 ))
121         (( tm[tm_hour] = sec / 60 ))
122         (( tm[tm_mday] = day + 1 ))
123         (( tm[tm_mon] = mon ))
124         (( tm[tm_year] = (year < 1 ? year - 1 : year) - 1900 ))
125         (( tm[tm_yday] = yday ))
126         (( tm[tm_isdst] = 0 ))
127         (( tm[tm_gmtoff] = 0 ))
128         tm[tm_zone]=UTC
129
130         print -r -- "${tm[@]}"
131 }
132
133 # $ mjd_implode tm_sec tm_min tm_hour tm_mday tm_mon tm_year \
134 #   ignored ignored ignored tm_gmtoff [ignored]
135 # ⇒ mjd sec
136 function mjd_implode {
137         local tm
138         set -A tm -- "$@"
139         local -i10 day x y sec
140
141         (( sec = tm[tm_sec] + 60 * tm[tm_min] + 3600 * tm[tm_hour] - \
142             tm[tm_gmtoff] ))
143         (( (day = tm[tm_year] + 1900) < 0 )) && (( ++day ))
144         (( y = day % 400 ))
145         (( day = (day / 400) * 146097 - 678882 + tm[tm_mday] ))
146         while (( sec < 0 )); do
147                 (( --day ))
148                 (( sec += 86400 ))
149         done
150         while (( sec >= 86400 )); do
151                 (( ++day ))
152                 (( sec -= 86400 ))
153         done
154         (( x = tm[tm_mon] ))
155         while (( x < 0 )); do
156                 (( --y ))
157                 (( x += 12 ))
158         done
159         (( y += x / 12 ))
160         (( x %= 12 ))
161         if (( x < 2 )); then
162                 (( x += 10 ))
163                 (( --y ))
164         else
165                 (( x -= 2 ))
166         fi
167         (( day += (306 * x + 5) / 10 ))
168         while (( y < 0 )); do
169                 (( day -= 146097 ))
170                 (( y += 400 ))
171         done
172         (( day += 146097 * (y / 400) ))
173         (( y %= 400 ))
174         (( day += 365 * (y % 4) ))
175         (( y /= 4 ))
176         (( day += 1461 * (y % 25) + 36524 * (y / 25) ))
177
178         print -- $day $sec
179 }
180
181 # convenience function to check (German) date input (no time-of-day)
182 # input is $2, MJD is written to $$1, normalised date to $$3 if $3 is set
183 function dtchk {
184         local tm mjd x saveIFS r iso=1 to
185         set -A tm
186         set -A mjd
187         set -A x
188         local -i10 -Z2 ra rb rc
189         local -i10 -Z4 ry
190
191         errstr="'$2' not in YYYY-MM-DD (ISO 8601) or DD.MM.YYYY (obsolete German) format"
192         if [[ $2 = +([0-9]).+([0-9]).+([0-9]) ]]; then
193                 iso=0
194         elif [[ $2 != +([0-9])-+([0-9])-+([0-9]) ]]; then
195                 return 1
196         fi
197         saveIFS=$IFS
198         IFS=.-
199         set -A x -- $2
200         IFS=$saveIFS
201         set -A tm -- 0 0 0 $((x[2])) $((x[1] - 1)) $((x[0] - 1900)) - - - 0 -
202
203         if (( !iso )); then
204                 tm[3]=$((x[0]))
205                 tm[5]=$((x[2] - 1900))
206         fi
207
208         set -A mjd -- $(mjd_implode "${tm[@]}")
209         set -A x -- $(mjd_explode "${mjd[0]}" 0)
210
211         (( ra = x[3] ))
212         (( rb = x[4] + 1 ))
213         (( ry = x[5] + 1900 ))
214         if (( iso )); then
215                 to=$ry-$rb-$ra
216         else
217                 to=$ra.$rb.$ry
218         fi
219
220         (( ra = x[2] ))
221         (( rb = x[1] ))
222         (( rc = x[0] ))
223
224         errstr="invalid date $2, normalises to $to $ra:$rb:$rc"
225         [[ ${tm[0]} = ${x[0]} ]] || return 1
226         [[ ${tm[1]} = ${x[1]} ]] || return 1
227         [[ ${tm[2]} = ${x[2]} ]] || return 1
228         errstr="bogus date $2, normalises to $to"
229         [[ ${tm[3]} = ${x[3]} ]] || return 1
230         [[ ${tm[4]} = ${x[4]} ]] || return 1
231         [[ ${tm[5]} = ${x[5]} ]] || return 1
232         errstr=
233         nameref r=$1
234         r=${mjd[0]}
235         if [[ -n $3 ]]; then
236                 nameref rr=$3
237                 rr=$to
238         fi
239         return 0
240 }