datefmt: Define 1 year as 365.2425 days instead of 365.25
[sunny256-utils.git] / goal
blobd8de8db72fd0ec0a66bdc85ae12e99943aef4779
1 #!/usr/bin/env perl
3 #=======================================================================
4 # goal
5 # File ID: efffd138-8a3f-11e5-8010-7927c6c13cb1
7 # Print timestamp when a specific goal will be reached.
9 # Character set: UTF-8
10 # ©opyleft 2015– Øyvind A. Holm <sunny@sunbase.org>
11 # License: GNU General Public License version 2 or later, see end of
12 # file for legal stuff.
13 #=======================================================================
15 use strict;
16 use warnings;
17 use Getopt::Long;
18 use Time::HiRes qw{ gettimeofday };
19 use Time::Local;
21 local $| = 1;
23 our %Opt = (
25 'current-time' => '',
26 'frac' => 0,
27 'help' => 0,
28 'quiet' => 0,
29 'verbose' => 0,
30 'version' => 0,
34 our $progname = $0;
35 $progname =~ s/^.*\/(.*?)$/$1/;
36 our $VERSION = '0.5.0';
38 Getopt::Long::Configure('bundling');
39 GetOptions(
41 'current-time|c=s' => \$Opt{'current-time'},
42 'frac|F' => \$Opt{'frac'},
43 'help|h' => \$Opt{'help'},
44 'quiet|q+' => \$Opt{'quiet'},
45 'verbose|v+' => \$Opt{'verbose'},
46 'version' => \$Opt{'version'},
48 ) || die("$progname: Option error. Use -h for help.\n");
50 $Opt{'verbose'} -= $Opt{'quiet'};
51 $Opt{'help'} && usage(0);
52 if ($Opt{'version'}) {
53 print_version();
54 exit(0);
57 exit(main());
59 sub main {
60 # {{{
61 my $Retval = 0;
62 if (!defined($ARGV[3])) {
63 warn("$progname: Missing arguments\n");
64 return(1);
66 my ($begintime, $beginval, $endval, $currval) = @ARGV;
67 my $begin_ep;
69 if ($begintime =~ /^(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d(\.\d+)?)$/) {
70 $begin_ep = timegm($6, $5, $4, $3, $2-1, $1);
71 } else {
72 warn("$progname: Invalid date format, " .
73 "must be \"YYYY-MM-DD HH:MM:SS[.sssss]\"\n");
74 return(1);
76 msg(2, "begin_ep = '$begin_ep'");
77 my $now;
78 if (length($Opt{'current-time'})) {
79 if ($Opt{'current-time'} =~
80 /^(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d(\.\d+)?)$/
81 ) {
82 $now = timegm($6, $5, $4, $3, $2-1, $1);
83 } else {
84 warn("$progname: Invalid date format in -c/--current-time\n");
85 return(1);
87 } else {
88 $now = gettimeofday();
90 if ($beginval == $currval) {
91 warn("$progname: Current value must be different from start value\n");
92 return(1);
94 my $end_ep = goal_x($endval, $begin_ep, $beginval, $now, $currval);
95 msg(2, "end_ep = '$end_ep'");
96 printf("%s %s\n",
97 sec_to_interval($end_ep - $now),
98 sec_to_string($end_ep),
101 return $Retval;
102 # }}}
103 } # main()
105 sub goal_x {
106 # Return x when y, x0, y0, x1 and y1 are known {{{
107 my ($y, # Goal to find timestamp for
108 $x0, # Start x
109 $y0, # Start y
110 $x1, # Current x
111 $y1, # Current y
112 ) = @_;
113 if ($y0 == $y1) {
114 # Avoid division by zero
115 return("NaN");
117 my $x = $x0 + ($y - $y0) / ($y1 - $y0) * ($x1 - $x0);
118 return($x);
119 # }}}
120 } # goal_x()
122 sub print_version {
123 # Print program version {{{
124 print("$progname $VERSION\n");
125 return;
126 # }}}
127 } # print_version()
129 sub sec_to_interval {
130 # {{{
131 my $origsecs = shift;
132 my $secs = 1.0 * abs($origsecs);
133 my $rem = 1.0 * $secs;
134 my $frac = sprintf("%.6f", 1.0 * $rem - int($rem));
135 if ($frac == 0) {
136 $frac = "";
137 } elsif ($frac =~ /^1/) {
138 $frac = "";
139 $rem = int($rem) + 1;
140 } else {
141 $frac =~ s/0+$//;
142 $frac =~ s/^0//;
144 my $days = 1.0 * int($rem / 86400.0);
145 $rem -= 1.0 * $days * 86400.0;
146 my $hours = 1.0 * int($rem / 3600.0);
147 $rem -= 1.0 * ($hours * 3600.0);
148 my $minutes = 1.0 * int($rem / 60.0);
149 $rem -= 1.0 * ($minutes * 60.0);
150 my $seconds = 1.0 * $rem;
151 my $retval = sprintf("%s%ud:%02u:%02u:%02u%s",
152 $origsecs < 0 ? "-" : "",
153 $days, $hours, $minutes, $seconds, $Opt{'frac'} ? $frac : '');
154 return($retval);
155 # }}}
156 } # sec_to_interval()
158 sub sec_to_string {
159 # Convert seconds since 1970 to "yyyy-mm-dd hh:mm:ss[.frac]Z" {{{
160 my ($Seconds, $Sep) = @_;
161 length($Seconds) || return('');
162 ($Seconds =~ /^-?(\d*)(\.\d+)?$/) || return(undef);
163 my $Secfrac = ($Seconds =~ /^([\-\d]*)(\.\d+)$/) ? 1.0*$2 : "";
164 $Secfrac =~ s/^0//;
166 defined($Sep) || ($Sep = " ");
167 my @TA = gmtime($Seconds);
168 my($DateString) = sprintf("%04u-%02u-%02u %02u:%02u:%02u%sZ",
169 $TA[5]+1900, $TA[4]+1, $TA[3],
170 $TA[2], $TA[1], $TA[0], $Opt{'frac'} ? $Secfrac : '');
171 return($DateString);
172 # }}}
173 } # sec_to_string()
175 sub usage {
176 # Send the help message to stdout {{{
177 my $Retval = shift;
179 if ($Opt{'verbose'}) {
180 print("\n");
181 print_version();
183 print(<<"END");
185 Print timestamp when a specific goal will be reached.
187 Usage: $progname [options] begin_date begin_val end_val current_val
189 All timestamps use UTC, and the date format must be specified as
190 "YYYY-MM-DD HH:MM:SS[.sssss]".
192 Options:
194 -c DATE, --current-time DATE
195 Use DATE as current time.
196 -F, --frac
197 Use fractional seconds.
198 -h, --help
199 Show this help.
200 -q, --quiet
201 Be more quiet. Can be repeated to increase silence.
202 -v, --verbose
203 Increase level of verbosity. Can be repeated.
204 --version
205 Print version information.
208 exit($Retval);
209 # }}}
210 } # usage()
212 sub msg {
213 # Print a status message to stderr based on verbosity level {{{
214 my ($verbose_level, $Txt) = @_;
216 if ($Opt{'verbose'} >= $verbose_level) {
217 print(STDERR "$progname: $Txt\n");
219 return;
220 # }}}
221 } # msg()
223 __END__
225 # This program is free software; you can redistribute it and/or modify
226 # it under the terms of the GNU General Public License as published by
227 # the Free Software Foundation; either version 2 of the License, or (at
228 # your option) any later version.
230 # This program is distributed in the hope that it will be useful, but
231 # WITHOUT ANY WARRANTY; without even the implied warranty of
232 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
233 # See the GNU General Public License for more details.
235 # You should have received a copy of the GNU General Public License
236 # along with this program.
237 # If not, see L<http://www.gnu.org/licenses/>.
239 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :