Issue
The goal is to convert the date format %Y-%m-%s %H:%M:%S %z
to seconds since EPOCH with POSIX tools.
I currently have a problem with the formula from the POSIX's href="https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_16" rel="nofollow noreferrer">Seconds Since the Epoch: I get a different result than GNU/BSD date
.
Here's my code, simplified for testing purposes:
#!/bin/bash
TZ=UTC date $'+ date: %Y-%m-%d %T %z \nexpect: %s \n %Y %j %H %M %S %z' |
awk '
NR <= 2 { print; next }
{
# $0: "YYYY jjj HH MM SS zzzzz"
$1 -= 1900
epoch = $5 + $4*60 + $3*3600 + int($2 + ($1-70)*365 + ($1-69)/4 - ($1-1)/100 + ($1+299)/400)*86400
print "result:", epoch
}
'
date: 2022-10-21 22:02:56 +0000
expect: 1666389776
result: 1666476176
What am I doing wrong?
Solution
By reading the specification thoroughly I found two problems:
tm_yday
value should bedate '+%j'
minus one:
days since January 1 of the year (tm_year)
- Integer arithmetic is needed:
The divisions in the formula are integer divisions
The following code now works; I also added the support for timezones in %z
format:
#!/bin/bash
date $'+ date: %Y-%m-%d %T %z \nexpect: %s \n %Y %j %H %M %S %z' |
awk '
NR <= 2 { print; next }
{
# $0: "YYYY jjj HH MM SS zzzzz"
tm_year = $1 - 1900
tm_yday = $2 - 1
tm_hour = $3
tm_min = $4
tm_sec = $5
zoffset = \
int(substr($6,1,1) "1") * \
(substr($6,2,2)*3600 + substr($6,4,2)*60)
epoch = \
tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 + \
(tm_year-70)*31536000 + int((tm_year-69)/4)*86400 - \
int((tm_year-1)/100)*86400 + int((tm_year+299)/400)*86400 - \
zoffset
print "result:", epoch
}
'
date: 2022-10-22 01:19:23 +0200
expect: 1666394363
result: 1666394363
ASIDE
Here's a sample implementation of GNU's mktime
function for POSIX awk
. It converts a date in the format %Y %m %d %H %M %S %z
to seconds since EPOCH:
function mktime(datespec, a) {
if ( !_mktime_init ) {
_mktime_init = 1
split("0 31 59 90 120 151 181 212 243 273 304 334",_mktime_moff," ")
}
# datespec: "YYYY mm dd HH MM SS zzzzz"
split(datespec,a," ")
a[7] = \
int(substr(a[7],1,1) "1") * \
(substr(a[7],2,2)*3600 + substr(a[7],4,2)*60)
a[3] += \
-1 + _mktime_moff[a[2]+0] + \
(a[2] > 2 && ((a[1]%4 == 0 && a[1]%100 != 0) || a[1]%400 == 0))
a[1] -= 1900
return \
a[6] + a[5]*60 + a[4]*3600 + a[3]*86400 + \
(a[1]-70)*31536000 + int((a[1]-69)/4)*86400 - \
int((a[1]-1)/100)*86400 + int((a[1]+299)/400)*86400 - \
a[7]
}
Sorry, I know that using meaningful variable names makes it easier to read the code but I made it compact for my personal ease of copy/paste and integration into my scripts.
Answered By - Fravadona Answer Checked By - Marie Seifert (WPSolving Admin)