-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathxdate
More file actions
executable file
·266 lines (237 loc) · 9.54 KB
/
xdate
File metadata and controls
executable file
·266 lines (237 loc) · 9.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#!/bin/sh
# This is a messy pile of posix shell, awk, & perl. TODO: all perl or no perl?
help() { # assemble --help from GNU date's --help plus extras
self="${0##*/}"
cat <</short_help
Usage: xdate [OPTION]... [+FORMAT]
Show the current time. Formats include YYYYMMDD_HHMMSS & xlsDDDDD[.DDD] (Excel)
-d, --date=STRING Display time described by STRING, not 'now'
--debug Annotate the parsed date, noting questionable usage
-f, --file=DATEFILE Like --date; once for each line of DATEFILE
-I, --iso-8601[=FMT] Output date/time in ISO 8601 format.
-R, --rfc-email Output date and time in RFC 5322 format.
--rfc-3339=FMT Output date/time in RFC 3339 format.
-r, --reference=FILE Display the last modification time of FILE
--ss-format=FMT Spreadsheet date format: \`excel\`, \`mac\`, or \`ooo\`
-s, --set=STRING Set time described by STRING
-u, --utc, --universal Print or set Coordinated Universal Time (UTC)
-x, --exec=PATH Run the date command with PATH, not \`date\`
-h, --help Display brief help and exit
-h, --long-help Display comprehensive help and exit
-V, --version Output version information and exit
/short_help
if [ "$1" = --long ]; then
cat <</long_help
Excel does not deal with time zones or daylight savings (DST). Because of that,
$self makes everything relative to the *current* DST offset. Converting back
to a standard date in $self may be an hour off, but it should work in Excel.
$self defaults to OpenOffice dates before 1899/12/31 and Excel afterwords:
--ss-format= NOTES DAY 1 DAY 59 DAY 60 Day 61
(default) OO then Excel 1899/12/31 1900/02/28 1900/02/28 1900/03/01
excel Leap year bug 1900/01/01 1900/02/28 1900/02/28 1900/03/01
openoffice 1899/12/31 1900/02/27 1900/02/28 1900/03/01
mac Excel for Mac 1904/01/01 1904/02/28 1904/02/29 1904/03/01
Learn more at https://stackoverflow.com/a/32578059/519360"
/long_help
"$gdate" --help 2>&1 |awk -v self="$self" '
function opt(option, desc) { return sprintf("%-*s%s", i, option, desc) }
function popt(option, desc) { print opt(option, desc) }
/^date: illegal/ { bsd = 1; next }
$1 == "FORMAT" { format_section = 1 }
! format_section { next }
/^Examples/i { examples = 1 }
/^Report bugs/ { print "Convert Excel time to a date"
print " $ xdate --date=12345\n" }
/(^|[,:$] +)date/ { sub (/date/, self) }
{ print }
$1 == "%j" { sub(/j/, "J"); sub(/ of.*/, "s after 1899/12/30"); print }
END { if (bsd) print "\nLearn more about formats: `man strftime`" }
'
echo "xdate wrapper home page: <https://github.com/adamhotep/misc-scripts>"
echo ""
else
echo ""
echo "Part of misc-scripts: https://github.com/adamhotep/misc-scripts"
fi
echo "xdate 0.3.20260315.0 copyright 2010+ by Adam Katz, GPLv3+"
exit
}
debug() { [ -n "$DEBUG" ] && echo "$*" >&2; }
xls_epoch=0 # calculated on first use since it's only used with -d and -f
# Usage: map_dates_filter
# Preprocessor for date's --date=STRING and --file=DATEFILE
# accept more date formats: YYYYMMDD_HHMMSS, DDDDD.DDD, xlsDDDDD[.DDD]
map_dates_filter() {
# Unix epoch offset of Excel epoch https://stackoverflow.com/a/32578059/519360
if [ "$xls_epoch" -ge 0 ]; then
# nested call works around Excel's lack of understanding of daylight savings
local TZ="$(date +%z)"
xls_epoch="$(( xls_epoch + $("$gdate" -d "1899/12/30 00:00:00 $TZ" +%s) ))"
fi
awk -v xls="$xls" -v xls_epoch="$xls_epoch" '
NF == 1 && $1 ~ /^[0-9]+_[0-9]+(\.[0-9]*)?$/ \
&& $1 ~ /^[12]...[01].[0-3]._[0-2].([0-6].([0-6].(\.[0-9]*)?)?)?$/ {
$0 = sprintf("%04d/%02d/%02d %02d:%02d:%02d\n",
substr($0, 1, 4), substr($0, 5, 2), substr($0, 7, 2),
substr($0, 10, 2), substr($0, 12, 2), substr($0, 14))
}
/^xls[+-]?[0-9]+(\.[0-9]*)?$/ { sub(/^xls\+?/, "", $1); x = 1 }
x || /^-?[0-9]+$/ && 2359 < $1/1 && $1/1 < 99999 || /^(-?[0-9]+\.[0-9]*)$/ {
if (($1 >= 0 || xls == 2) && $1 < 60 && xls) $1++; # Excel bug
$0 = sprintf("@%.0f\n", xls_epoch + $1 * 86400)
x = 0
}
1
' "$@"
return
# why 2360-100101? because 2359 = "today at 23:59" and 100101 = "2010/01/01"
#perl -ne '
# if (/^[12]\d\d\d[01]\d[0-3]\d_[0-2]\d([0-6]\d([0-6]\d)?)?(\s|$)/) {
# printf "%04d/%02d/%02d %02d:%02d:%02d\n",
# substr($_,0,4), substr($_,4,2), substr($_,6,2),
# substr($_,9,2), substr($_,11,2), substr($_,13);
# } elsif (2359 < $_ && $_ < 100101 || /^-?[0-9]*\.[0-9]*$/) {
# $_++ if ($_ >= 0 or $ENV{xls} == 2) and $_ < 60 and $ENV{xls}; # Excel bug
# printf "@%.0f\n", $ENV{xls_epoch} + $_ * 86400
# } else { print; }
#' "$@"
}
# convert file or string to mapped version
tmp=
trap_codes="0 1 2 5 9 11 15 18"
trap "rm -f $tmp" $trap_codes # has to be outside the function
map_dates() {
if [ -t 0 ] && ! [ -e "$1" ]; then # no standard input, not given a file
printf "%s\n" "$@" |map_dates_filter
else
tmp="$(mktemp "${TMPDIR:-/tmp}/xdate.${pid}XXXX")"
map_dates_filter "$@" > "$tmp"
echo "$tmp"
fi
}
# Getopts can't handle altering values, nor can it handle optional arguments
reset= next= xls=1 pid=$$ gdate=date long=
for arg in "$@"; do
debug "HERE looping on <$arg>"
if [ -z "$reset" ]; then reset=1; set --; fi
if [ -n "$next" ]; then
case $next in
( -[df] ) set -- "$@" "$next" "$(map_dates "$arg")" ;;
( -x ) gdate="$arg" ;;
( --ss-format ) ss_fmt="$arg" ;;
( -* ) set -- "$@" "$next" "$arg" ;;
esac
next=
continue
fi
if [ "$arg" != "${arg#-[!-]}" ]; then
opt="${arg#-}"
while [ -n "$opt" ]; do
case $opt in
( d ) next=-d ;;
( d?* ) set -- "$@" -d "$(map_dates "${opt#d}")"; break ;;
( f ) next=-f ;;
( f?* ) set -- "$@" -f "$(map_dates "${opt#f}")"; break ;;
( h* ) help=1 ;;
( [rs] ) next=-"$opt" ;;
( [Ir]* ) set -- "$@" "-$opt"; break ;;
( R* ) set -- "$@" -R ;;
( u* ) set -- "$@" -u; export TZ=UTC ;;
( [Vv]* ) version ;;
( x ) next=-x ;;
( x* ) gdate="${opt#x}"; break ;;
( * ) rest="${opt#?}"; set -- "$@" "-${opt%$rest}" ;;
esac
opt="${opt#?}"
done
else
case $arg in
( +*%*J* ) set -- "$@" "$(echo "$arg" |awk -v pid="$pid" '{
gsub(/(^|[^%])%[ #+-]*[0-9]*(\.[0-9]*)?J/, "&<" pid "<%s%z>"); print
}')" ;;
#( +*%*J* ) set -- "$@" "$(echo "$arg" |perl -pe \
# "s/(?<!%)(%(?:[ #+-]*\d*(?:\.\d*)?)?J)/\$1<$pid<%s%z>/g")" ;;
( [0-9+]* ) set -- "$@" "$arg" ;;
( --date ) next=-d ;;
( --date=* ) set -- "$@" --date="$(map_dates "${arg#--date=}")" ;;
( --debug ) DEBUG=1; set -- "$@" $arg ;;
( --exec=?* ) gdate="${arg#*=}"; debug "set gdate='$gdate'" ;;
( --exec ) next=-x ;;
( --file ) next=-f ;;
( --file=* ) set -- "$@" --file="$(map_dates "${arg#--file=}")" ;;
( --help ) help=1 ;;
( --*help* ) help --long ;;
( --long ) long=--long ;;
( --rfc-3339 ) next=$arg ;;
( --ss-format ) next=$arg ;;
( --ss-format=* ) ss_fmt="${arg#*=}" ;;
( --utc | --univ* ) export TZ=UTC; set -- "$@" "$arg" ;;
( --version ) version ;;
( * ) set -- "$@" "$arg" ;;
esac
fi
done
case $ss_fmt in
( '' ) : "using default openoffice format" ;;
( e | excel* | x | xl | xls* | ms* | microsoft* ) xls=2 ;;
( m | ma | mac* | 1904* ) xls_mac=1 xls_epoch=126316800 xls=0 ;;
( [ol] | ooo | [ol]*office* ) xls=0 ;;
( * ) echo "${0##*/}: invalid argument '$ss_fmt' for '--ss-format'" >&2
echo "Valid arguments are:" >&2
echo " - 'excel' or 'xls' or 'ms'" >&2
echo " - 'mac' or '1904'" >&2
echo " - 'libreoffice' or 'openoffice' or 'ooo'" >&2
echo "Try '${0##*/} --help' for more information." >&2
exit 2
;;
esac
export xls DEBUG xls_mac TZ
if [ -n "$help" ]; then
help $long
elif [ -z "$tmp" ]; then # remove the trap if it's not needed
trap - $trap_codes
fi
if ! "$gdate" -d now >/dev/null 2>&1; then
we_have() { command -v "$1" >/dev/null 2>&1; } # true if we have the command
if [ "$gdate" = date ]; then
if we_have gdate && gdate -d now >/dev/null 2>&1; then
gdate=gdate
debug "We are now set to use \`gdate\` (GNU) rather than \`date\` (?)"
elif we_have busybox && busybox date -d now >/dev/null 2>&1; then
bdate() { busybox date "$@"; }
gdate=bdate
debug "We are now set to use \`busybox date\` rather than \`date\`"
fi
else
echo "${0##*/}: failed to successfully run \`$gdate -d now\`" >&2
exit 2
fi
fi
"$gdate" "$@" |perl -wpe 'use strict;
my $re =
qr/(?<!%)%([-_^#]*\d*(?:\.\d*)?[EO]?)?J<'$pid'<(-?\d+)([+-])0?(\d+)(\d\d)>/;
if (my @d = /$re/) {
my $sign = ($d[2] eq "+" ? 1 : -1);
my $J = $d[1]/86400 + 25569 + $sign * ($d[3] + $d[4]/60) / 24;
if ($ENV{DEBUG}) {
print STDERR $_ . qq(1="$d[0]" 2="$d[1]" 3="$d[2]" 4="$d[3]" 5="$d[4]"\n)
. "$d[1]/86400 + 25569 + $sign * ($d[3] + $d[4]/60) / 24 = $J\n";
}
# adjust for daylight savings: localtime()[8] = is_dst
my @now = localtime();
my @then = localtime($d[1]);
$J += ($now[8] - $then[8]) / 24;
# adjust for Excel bug
$J-- if $ENV{xls} and $J < 61 and ($ENV{xls} == 2 or $J > 0);
$J -= 1462 if $ENV{xls_mac};
# format as specified
if ($d[0] ne "") {
$J = sprintf("%".$d[0]."f", $J);
} else {
$d[0] = ".6";
$J = sprintf("%".$d[0]."f", $J);
$J =~ s/\.?0+$//;
}
s/$re/$J/g;
}
'