├── src
├── suncron.sh
├── suncron.conf
├── lib
│ ├── ConfigFile.pm
│ ├── CalcTime.pm
│ └── Astro
│ │ └── Sunrise.pm
└── suncron.pl
├── Makefile
├── README.md
└── LICENSE
/src/suncron.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Generate a cron file based on when sun rises or sets
4 | SUNCRON_PATH/bin/suncron
5 |
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | INSTALL_DIR ?= /usr
2 |
3 | .PHONY: default install uninstall
4 |
5 | default:
6 | @echo "make install|uninstall"
7 |
8 | install:
9 | @echo "Installing..."
10 | @install -d ${INSTALL_DIR}/bin
11 | @install -d ${INSTALL_DIR}/share/suncron/lib/Astro
12 | @install -d /etc/default
13 | @install -m755 src/suncron.pl ${INSTALL_DIR}/bin/suncron
14 | @sed < src/suncron.sh >/tmp/suncron.sh -e "s#SUNCRON_PATH#${INSTALL_DIR}#"
15 | @install -m755 /tmp/suncron.sh /etc/cron.daily/suncron
16 | @rm -f /tmp/suncron.sh
17 | @install -m644 src/suncron.conf /etc/default/suncron
18 | @install -m644 README.md ${INSTALL_DIR}/share/suncron
19 | @install -m644 LICENSE ${INSTALL_DIR}/share/suncron
20 | @install -m755 src/lib/CalcTime.pm ${INSTALL_DIR}/share/suncron/lib
21 | @install -m755 src/lib/ConfigFile.pm ${INSTALL_DIR}/share/suncron/lib
22 | @install -m755 src/lib/Astro/Sunrise.pm ${INSTALL_DIR}/share/suncron/lib/Astro
23 | @echo "Done."
24 | @echo "A sample configuration file has been copied to '/etc/default/suncron'."
25 | @echo "Existing file has a '~' appended."
26 | @echo "You need to change the coordinates and time zone information in order to get"
27 | @echo "correct calculation for sunset and sunrise. The configuration file also contain"
28 | @echo "two sample entries showing the format of rules."
29 | @echo "A cron script has been added to /etc/cron.daily so that new entris are made"
30 | @echo "every day. Verify the daily cron jobs are run at about midnight."
31 |
32 | uninstall:
33 | @echo "Uninstalling..."
34 | @rm -f /etc/cron.daily/suncron
35 | @rm -f /etc/cron.d/suncron
36 | @rm -f ${INSTALL_DIR}/bin/suncron
37 | @rm -f /etc/default/suncron
38 | @rm -Rf ${INSTALL_DIR}/share/suncron
39 | @echo "Done"
40 |
41 |
--------------------------------------------------------------------------------
/src/suncron.conf:
--------------------------------------------------------------------------------
1 | #
2 | # Sample configuration file for SunCron
3 | #
4 | # This file is expected to be located in /etc/default unless you use the
5 | # command line option to override this.
6 | #
7 |
8 | #
9 | # Location and time zone informatino is needed to calculate a correct sunrise
10 | # and sunset.
11 | # The coordinates should be decimal degrees.
12 | # Valid timezones are found in /usr/share/zoneinfo
13 | #
14 | [Location]
15 | Longitude=17.3
16 | Latitude=59.05
17 | Timezone=Europe/Stockholm
18 |
19 |
20 | # The following section lists the rules that are transformed into cron entries.
21 | #
22 | # Each rules has the following format:
23 | # condition;true;false;cron part
24 | # - condition is a formula like 'sunrise - 01:00 < 07:00'
25 | # - true is the time if condition was true
26 | # - false is the time if condition was false
27 | # - cron part is a crontab line not including minute and hour field (the first
28 | # two fields).
29 | #
30 | # True and false can be empty.
31 | # True and false may be a simple calculation (like 'sunrise - 01:00')
32 | # Keep formulas simple in both conditon and true/false
33 | #
34 | [Rules]
35 | # Below are two samples, showing the rules and the resulting entry
36 | #
37 | # conditon: sun rises after 07:00
38 | # if true: execute at 06:15
39 | # if false: do nothing
40 | # cron: every day, run 'echo TEST>/dev/null' as user 'root'
41 | # This is what the resulting entry would look like:
42 | sunrise > 07:00 ; 06:15 ; ; * * * root echo TEST>/dev/null
43 |
44 | #
45 | # condition sun sets before 18:00
46 | # if true: execute at 17:00,
47 | # if false: execute an hour before sunset
48 | # cron: every saturday and sunday, run 'ls >/dev/null /root' as user root
49 | # This is what the resulting entry would look like:
50 | sunset < 18:00 ; 17:00 ; sunset - 01:00 ; * * 6-7 root ls >/dev/null /root
51 |
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SunCron
2 | Generate cron entries from rules based on sunset and/or sunrise
3 |
4 | SunCron is a perl program that will generate cron rules based on the time when sun sets and/or rises. I made it to control the lighting in my apartment together with the nexa remote control I made. You can however run any command that doesn’t require user input.
5 |
6 | ## Requirements:
7 | - Perl
8 | - Astro::Sunrise module (included in this package)
9 | - DateTime module (available from CPAN)
10 |
11 | ## Installation:
12 | - Run 'make install' as root.
13 | - Edit /etc/default/suncron
14 | - Verify that daily cron jobs execute sometime between 00:00 and 01:00 or
15 | you'll risk missing the sunset. In this case you'll be a day late, that
16 | is, the sunrise and sunset times will be for previous day.
17 |
18 | ## Usage:
19 | The idea behind this project is to execute programs at sunset or sunrise.
20 | Instead of running an entire program to wait for sunset or sunrise, this
21 | program is supposed to execute at midnight and calculate todays sunrise and
22 | sunset times. It will then parse a configuration file with a set of rules
23 | and update the cron file with the new values.
24 |
25 | The configuration file is found in '/etc/default/suncron.conf'.
26 | It has a Location section and a rule section. Each rule consists of four
27 | parts:
28 | - a condition
29 | The condition is a clear text formula describing the condition,
30 | eg. 'sunset < 17:00' to test if sun sets before 17:00.
31 |
32 | - a "true" and a "false" statement
33 | these statements is the time to use if condition is true/false,
34 | eg. 'sunrise + 03:30' for three and a half hour after sunrise.
35 | Or you can leave it empty to ignore it.
36 |
37 | - a cron part
38 | the cron part is a cron line without the minute and hour field,
39 | eg. '* * [1-4] root /path/to/command arg'
40 |
41 | The resulting cron data is written to /etc/cron.d/suncron (unless you
42 | redirect it with the command line switch, see 'suncron --help').
43 | Conditions that evaluate to a true/false state that is empty will NOT
44 | be written to the file.
45 |
46 | ## Known issues:
47 | The program makes no attempt at parsing the cron rule, so rules that
48 | have a cron part that is to execute at, say, every thursday will be
49 | written every day. This does not mean it will be executed every day,
50 | since cron will handle this correctly. It's just that suncron makes
51 | an unnecessary line in the cron file. I'm lazy, so you'll have to
52 | accept this ;-)
53 |
--------------------------------------------------------------------------------
/src/lib/ConfigFile.pm:
--------------------------------------------------------------------------------
1 | package ConfigFile;
2 |
3 | # This file is part of SunCron.
4 | #
5 | # SunCron is free software; you can redistribute it and/or modify
6 | # it under the terms of the GNU Gerenral Public License as published by
7 | # the Free Software Foundation; either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # SunCron is distributed in the hope that it will be usefull,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with SunCron; if not, write to the Free Software
17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 | #
19 | # Copyright: (C) 2009-2010 Johan Stenarson
20 |
21 | use strict;
22 | use warnings;
23 |
24 | sub new {
25 | my ($package, $file) = @_;
26 | my ($latitude, $longitude, $timezone);
27 | my $section = "none";
28 | my @rules = ();
29 |
30 | die('Error: Need a filename to configuration file') if not defined($file);
31 |
32 | my $conf;
33 | my $result = open($conf, "<", $file);
34 |
35 | if($result) {
36 | while(<$conf>) {
37 | if(!/^\s*#/ && !/^\s*$/) {
38 | if(/^\s*\[(\w*)\]\s*/) {
39 | $section = lc $1;
40 | } else {
41 | if($section eq "location") {
42 | (my $key, my $value) = split(/=/, $_, 2);
43 | $value =~ s/^\s*//;
44 | $value =~ s/\s*$//;
45 | $key =~ s/^\s*//;
46 | $key =~ s/\s*$//;
47 | if(lc $key eq "longitude") {
48 | $longitude = $value;
49 | } elsif(lc $key eq "latitude") {
50 | $latitude = $value;
51 | } elsif(lc $key eq "timezone") {
52 | $timezone = $value;
53 | }
54 | } elsif (lc $section eq "rules") {
55 | # do the magix
56 | s/^\s*//;
57 | s/\s*$//;
58 | push @rules, ($_);
59 | } else {
60 | die("Unknown section: $section\n");
61 | }
62 | }
63 | }
64 | }
65 | } else {
66 | die("Couldn't open configuration file: '$file'");
67 | }
68 |
69 | my $self = { _file => $file, _latitude => $latitude, _longitude => $longitude, _rules => \@rules, _timezone => $timezone };
70 |
71 | bless($self, $package);
72 |
73 | return $self;
74 | }
75 |
76 | sub latitude {
77 | my $self = shift;
78 |
79 | return $self->{_latitude};
80 | }
81 |
82 | sub longitude {
83 | my $self = shift;
84 |
85 | return $self->{_longitude};
86 | }
87 |
88 | sub timezone {
89 | my $self = shift;
90 |
91 | return $self->{_timezone};
92 | }
93 |
94 | sub get_rules {
95 | my $self = shift;
96 |
97 | return $self->{_rules};
98 | }
99 |
100 | 1;
101 | __END__
102 |
--------------------------------------------------------------------------------
/src/suncron.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl -w
2 |
3 | # This file is part of SunCron.
4 | #
5 | # SunCron is free software; you can redistribute it and/or modify it
6 | # under the terms of the GNU Gerenral Public License as published by
7 | # the Free Software Foundation; either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # SunCron is distributed in the hope that it will be usefull,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with SunCron; if not, write to the Free Software
17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 | #
19 | # Copyright: (C) 2009-2010 Johan Stenarson
20 |
21 | #
22 | # Are we installed?
23 | #
24 | use lib (-e '/usr/share/suncron/' ? '/usr/share/suncron/lib' : $ENV{PWD}.'/lib');
25 |
26 | use strict;
27 | use warnings;
28 |
29 | use Data::Dumper;
30 | use Getopt::Long;
31 | use Pod::Usage;
32 |
33 | # Internal modules
34 | use ConfigFile;
35 | use CalcTime;
36 |
37 | my $sun_never;
38 | local $SIG{__WARN__} = sub {
39 | # Catch warning from Astro if sun never sets or rises
40 | if($_[0] =~ /Sun never (\w*)!*/) {
41 | $sun_never = $1;
42 | } else {
43 | warn $_[0];
44 | }
45 | };
46 |
47 | my $NAME = 'SunCron';
48 | my $VERSION = '0.1';
49 |
50 | my $config_file = '/etc/default/suncron';
51 | my $cron_file = '/etc/cron.d/suncron';
52 |
53 | # Parse command line arguments
54 | my ($version, $help, $man, $init, $verbose);
55 | my $result = GetOptions(
56 | 'conf=s' => \$config_file,
57 | 'cron=s' => \$cron_file,
58 | 'help' => \$help,
59 | 'man' => \$man,
60 | 'verbose' => \$verbose,
61 | 'version' => \$version,
62 | );
63 |
64 | pod2usage(1) if $help;
65 | pod2usage(-exitstatus => 0, -verbose => 2) if $man;
66 | if($version || $verbose) {
67 | print <new($config_file);
78 |
79 | print "Creating time expression parser...\n" if $verbose;
80 | my $parser = CalcTime->new( longitude => $config->longitude, latitude => $config->latitude, timezone => $config->timezone );
81 |
82 | open(my $cron, ">", $cron_file);
83 |
84 | print $cron "#\n# WARNING! This file is automatically generated, changes will be lost\n#\n";
85 |
86 | my @rules = @{$config->get_rules()} or die("No rules defined in configuration\n");
87 | for my $rule (@rules) {
88 | print "Parsing rule: $rule\n" if $verbose;
89 |
90 | my @parts = split(/;/, $rule);
91 | my $time;
92 |
93 | # Parse condition
94 | my $result = $parser->evaluate($parts[0]);
95 |
96 | last if defined $sun_never;
97 |
98 | if($result) {
99 | $time = $parser->evaluate($parts[1]);
100 | } else {
101 | $time = $parser->evaluate($parts[2]);
102 | }
103 | if(defined $time) {
104 | print $cron $time->minute()." ".$time->hour()." ".$parts[3]."\n";
105 | }
106 | }
107 |
108 | print $cron "# File is empty because sun never $sun_never\n" if defined $sun_never;
109 |
110 | close $cron;
111 |
112 | print "Done!\n" if $verbose;
113 |
114 | exit;
115 |
116 | =head1 NAME
117 |
118 | SunCron - Tell cron to run commands at times based on sunset and sunrise
119 |
120 | =head1 SYNOPSIS
121 |
122 | suncron [options...]
123 |
124 | Options:
125 | --conf=FILE filename of configuration file
126 | --cron=FILE filename of cron file
127 | --help show brief help text then exit
128 | --man show full documentation then exit
129 | --verbose show verbose information
130 | --version output version information and exit
131 |
132 | =head1 OPTIONS
133 |
134 | =over
135 |
136 | =item B<--conf>
137 |
138 | Full path to configuration file. Default is F.
139 |
140 | =item B<--cron>
141 |
142 | Where to write cron file. Default is F.
143 |
144 | =item B<--help>
145 |
146 | Show a brief help text and exit.
147 |
148 | =item B<--man>
149 |
150 | Show documentation and exit.
151 |
152 | =item B<--verbose>
153 |
154 | Show information about what program is doing.
155 |
156 | =item B<--version>
157 |
158 | Show version number and exit.
159 |
160 | =back
161 |
162 | =head1 DESCRIPTION
163 |
164 | This program is expected to be run daily at midnight and will then calculate
165 | the sunrise and sunset. This information is used when evaluating rules in the
166 | configuration file and the result will be used to generate cron entries in a
167 | cron file. In other words, with this program you'll be able to tell cron to
168 | run commands at sunrise or sunset.
169 |
170 | The configuration file is found in '/etc/default/suncron.conf'.
171 | It has a B section and a B section. Each rule consists of four
172 | parts:
173 |
174 | =over
175 |
176 | =item B
177 |
178 | The condition is a clear text formula describing the condition, eg. 'sunset < 17:00' to test if sun sets before 17:00.
179 |
180 | =item B and B
181 |
182 | These statements is the time to use if condition is true/false, eg. 'sunrise + 03:30' for three and a half hour after sunrise.
183 |
184 | =item B
185 |
186 | The cron part is a cron line without the minute and hour field, eg. '* * [1-4] root /path/to/command arg'. The resulting cron data is written to /etc/cron.d/suncron (unless you redirect it with the --cron switch).
187 |
188 | =head1 REQUIREMENTS
189 |
190 | This project depend on the following software:
191 |
192 | =over
193 |
194 | =item B
195 |
196 | Perl extension for computing the sunrise/sunset on a given day
197 |
198 | =item B
199 |
200 | A date and time object
201 |
202 | =back
203 |
204 | =head1 AUTHOR
205 |
206 | Johan Stenarson Ejohan.stenarson@gmail.comE
207 |
208 | =head1 BUGS
209 |
210 | Probably, but none that I know of.
211 |
212 | =head1 COPYRIGHT
213 |
214 | Copyright (C) 2009-2010 Johan Stenarson. All rights reserved.
215 | This program is free software; you can redistribute it and/or modify it.
216 |
217 | The full text of the license can be found in the copyright file included with this package.
218 |
219 | =cut
220 |
221 |
--------------------------------------------------------------------------------
/src/lib/CalcTime.pm:
--------------------------------------------------------------------------------
1 | package CalcTime;
2 |
3 | # This file is part of SunCron.
4 | #
5 | # SunCron is free software; you can redistribute it and/or modify
6 | # it under the terms of the GNU Gerenral Public License as published by
7 | # the Free Software Foundation; either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # SunCron is distributed in the hope that it will be usefull,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with SunCron; if not, write to the Free Software
17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 | #
19 | # Copyright: (C) 2009-2010 Johan Stenarson
20 |
21 | use warnings;
22 | use strict;
23 |
24 | use DateTime;
25 | use Astro::Sunrise;
26 |
27 | our $VERSION = '0.1';
28 |
29 | #
30 | # The following variables are allowed in an expression
31 | #
32 | my %params = (
33 | 'time' => 1, # Replaced with current time
34 | 'sunset' => 1, # Replaced with time of sunset
35 | 'sunrise' => 1 # Replaced with time of sunrise
36 | );
37 |
38 | #
39 | # The following operators are allowed in an expression
40 | # The higher value, the later the expression is evaluated.
41 | #
42 | my %operators = (
43 | '-' => 1, # Substract two operands
44 | '+' => 2, # Add two operands
45 | '<' => 3, # Test left side is less than right side
46 | '>' => 4, # Test left side is less than right side
47 | 'or' => 5, # One or both must be true
48 | 'and' => 6, # Both peft and right must be true
49 | );
50 |
51 |
52 |
53 | =head1 USAGE
54 |
55 | =over
56 |
57 | =item B
58 |
59 | =over
60 |
61 | =item Cnew();>
62 | =item Cnew( time =E DateTime-Enow(), longitude =E 12.345, latitude =E 34.567 );>
63 |
64 | =back
65 |
66 | Create a new CalcTime object. The time parameter will override the default value, which is
67 | to use the current time at object creation. Longitude and latitude values need to be set
68 | if sunrise or sunset are used in the expressions.
69 |
70 | =cut
71 |
72 | sub new {
73 | my ($class, %args) = @_;
74 |
75 | my $timezone = $args{'timezone'} || 'UTC';
76 | my $time = $args{'time'} || DateTime->now(time_zone => $timezone);
77 | my $latitude = $args{'latitude'} || 0.0;
78 | my $longitude = $args{'longitude'} || 0.0;
79 |
80 | my $self = { _time => $time, _latitude => $latitude, _longitude => $longitude };
81 |
82 | return bless($self, $class);
83 | }
84 |
85 | =item B
86 |
87 | =over
88 |
89 | =item C<$parser-Eset_datetime(DateTime-Enow());>
90 |
91 | =back
92 |
93 | This will set the time to use instead of current time, which is default if no
94 | time is given.
95 |
96 | =cut
97 |
98 | sub set_datetime ($) {
99 | my ($self, $time) = @_;
100 |
101 | $self->{'_time'} = $time;
102 | }
103 |
104 | =item B
105 |
106 | =over
107 |
108 | =item C<$parser-Eset_coordinate( 12.345, 23.456 );>
109 |
110 | =back
111 |
112 | This will set the coordinates used to calculate sunset and sunrise.
113 | First parameter is longitude and second parameter is latitude.
114 | Both are in decimal degrees
115 |
116 | =cut
117 |
118 | sub set_coordinate ($$) {
119 | my ($self, $longitude, $latitude) = @_;
120 |
121 | $self->{'_longitude'} = $longitude;
122 | $self->{'_latitude'} = $latitude;
123 | }
124 |
125 | =item B
126 |
127 | =over
128 |
129 | =item C<$parser-Eevaluate("time E 07:00 and time E sunrise");>
130 |
131 | =back
132 |
133 | Test if time expression is true and if so, return the final DateTime
134 | object.
135 |
136 | =cut
137 |
138 | sub evaluate {
139 | my ($self, $expression) = @_;
140 | my $result = 1;
141 |
142 | my @parts = $self->_prepare($expression);
143 |
144 | $result = $self->_calculate(@parts);
145 |
146 | if(ref($result) eq 'DateTime::Duration') {
147 | my $tmp = $self->{_time}->clone();
148 | $tmp->set_hour($result->hours);
149 | $tmp->set_minute($result->minutes);
150 | $result = $tmp;
151 | }
152 |
153 | return $result;
154 | }
155 |
156 | =item B
157 |
158 | =over
159 |
160 | =item C<$parser-Ecrosses_horizon();>
161 |
162 | =back
163 |
164 | Test if sun goes up and down (not midnight sun or polar night)
165 |
166 | =cut
167 |
168 | sub crosses_horizon {
169 | my $self = shift;
170 |
171 | if(not defined $self->{_sunset}) {
172 | $self->_calc_sun();
173 | }
174 |
175 | return (($self->{_sunset}->hours != $self->{_sunrise}->hours) and ($self->{_sunset}->minutes != $self->{_sunrise}->minutes));
176 | }
177 |
178 | #
179 | # Convert string format to an array of elements. The order will be a postfix
180 | # order for easier calculation.
181 | # Each element will be tested if it is an allowed operation or variable.
182 | #
183 | sub _prepare {
184 | my ($self, $rule) = @_;
185 | my @result = ();
186 | my @stack = ();
187 |
188 | # Insert spaces for easier split
189 | $rule =~ s/ \< /g;
190 | $rule =~ s/>/ \> /g;
191 | $rule =~ s/\+/ + /g;
192 | $rule =~ s/-/ - /g;
193 | $rule =~ s/and/ and /g;
194 | $rule =~ s/or/ or /g;
195 | $rule =~ s/ [ ]+/ /g;
196 | $rule =~ s/^ *//;
197 | $rule =~ s/ *$//;
198 |
199 | my @parts = split(/ /, $rule);
200 |
201 | foreach my $item (@parts) {
202 | if( ($item =~ m/[\d]{1,2}:[\d]{1,2}/) or $params{$item} ) {
203 | push( @result, $item );
204 | } else {
205 | OPERATOR: while(@stack) {
206 | die("Syntax error: Unknown expression '$item'\n") if not defined($operators{$item});
207 | if($operators{$item} > $operators{$stack[0]}) {
208 | push(@result, pop(@stack));
209 | } else {
210 | last OPERATOR;
211 | }
212 | }
213 | push(@stack, $item);
214 | }
215 | }
216 |
217 | while(@stack) {
218 | push(@result, pop(@stack));
219 | }
220 |
221 | return @result;
222 | }
223 |
224 | #
225 | # Parse an element.
226 | #
227 | # Elements can be a variable (time, sunrise or sunset) or a time (HH:MM).
228 | #
229 | sub _parse_parameter {
230 | my ($self, $parameter) = @_;
231 | my $result;
232 |
233 | if($parameter eq "time") {
234 | $result = $self->{_time}->clone();
235 | } elsif($parameter eq "sunset") {
236 | if(not defined $self->{_sunset}) {
237 | $self->_calc_sun();
238 | }
239 | $result = $self->{_sunset}->clone();
240 | } elsif($parameter eq "sunrise") {
241 | if(not defined $self->{_sunrise}) {
242 | $self->_calc_sun();
243 | }
244 | $result = $self->{_sunrise}->clone();
245 | } elsif($parameter =~ m/[\d]{1,2}:[\d]{1,2}/) {
246 | # Try parsing it as a HH:MM value
247 | my @items = split(/:/, $parameter);
248 | $result = DateTime::Duration->new( hours => $items[0], minutes => $items[1] );
249 | }
250 |
251 | return $result;
252 | }
253 |
254 | sub _calc_sun {
255 | my $self = shift;
256 |
257 | #
258 | # NOTE! Daylight Saving parameter is set to 0, since offset is adjusted
259 | # accordingly from DateTime object, that is DST is included in offset.
260 | #
261 | my ($sunrise, $sunset) = sunrise($self->{_time}->year, $self->{_time}->month, $self->{_time}->day,
262 | $self->{_longitude}, $self->{_latitude}, $self->{_time}->offset / 3600, 0);
263 |
264 | $self->{_sunrise} = $self->_parse_parameter($sunrise);
265 | $self->{_sunset} = $self->_parse_parameter($sunset);
266 | }
267 |
268 | #
269 | # Calculate the postfix expression
270 | #
271 | sub _calculate {
272 | my ($self, @expr) = @_;
273 | my @stack = ();
274 |
275 | foreach my $item (@expr) {
276 | if( ($item =~ m/[\d]{1,2}:[\d]{1,2}/) or $params{$item} ) {
277 | push(@stack, $self->_parse_parameter($item));
278 | } else {
279 | my $rhs = pop(@stack);
280 | my $lhs = pop(@stack);
281 | push(@stack, $self->_do_operator($item, $lhs, $rhs));
282 | }
283 | }
284 |
285 | return pop(@stack);
286 | }
287 |
288 | sub _do_operator {
289 | my ($self, $operator, $lhs, $rhs) = @_;
290 | my $result;
291 |
292 | if($operator eq "+") {
293 | $lhs->add_duration( $rhs );
294 | $result = $lhs;
295 | } elsif( $operator eq "-") {
296 | $lhs->subtract_duration( $rhs );
297 | $result = $lhs;
298 | } elsif( $operator eq ">") {
299 | # We can't compare a real time with a duration. Treat duration as a
300 | # real time.
301 | if($lhs->isa('DateTime::Duration')) {
302 | my $tmp = $self->{_time}->clone()->set( hour => $lhs->hours, minute => $lhs->minutes );
303 | $lhs = $tmp;
304 | }
305 | if($rhs->isa('DateTime::Duration')) {
306 | my $tmp = $self->{_time}->clone()->set( hour => $rhs->hours, minute => $rhs->minutes );
307 | $rhs = $tmp;
308 | }
309 | $result = DateTime->compare( $lhs, $rhs ) > 0;
310 | } elsif( $operator eq "<") {
311 | if($lhs->isa('DateTime::Duration')) {
312 | my $tmp = $self->{_time}->clone()->set( hour => $lhs->hours, minute => $lhs->minutes );
313 | $lhs = $tmp;
314 | }
315 | if($rhs->isa('DateTime::Duration')) {
316 | my $tmp = $self->{_time}->clone()->set( hour => $rhs->hours, minute => $rhs->minutes );
317 | $rhs = $tmp;
318 | }
319 | $result = DateTime->compare( $lhs, $rhs ) < 0;
320 | } elsif( $operator eq "or") {
321 | $result = ( $lhs or $rhs );
322 | } elsif( $operator eq "and") {
323 | $result = ( $lhs and $rhs );
324 | } else {
325 | die("Syntax error: Unknown operator '$operator'\n");
326 | }
327 |
328 | #$result = 0 if not defined $result;
329 |
330 | #print _debug_item($lhs) . " $operator " . _debug_item($rhs) . " = " . _debug_item($result) . "\n";
331 |
332 | return $result;
333 | }
334 |
335 | sub _debug_item {
336 | my $item = shift;
337 |
338 | if(ref($item) eq 'DateTime::Duration') {
339 | return $item->hours . ":" . $item->minutes;
340 | }
341 |
342 | return $item;
343 | }
344 |
345 | __END__
346 | =head1 NAME
347 |
348 | CalcTime - An object that compute simple time expressions
349 |
350 | =head1 SYNOPSIS
351 |
352 | use CalcTime;
353 |
354 | $parser = CalcTime->new();
355 | $parser = CalcTime->new(
356 | time => DateTime->new( month => 1, day => 1 ),
357 | longitude => 17.345,
358 | latitude => 53.123
359 | );
360 |
361 | $parser->set_datetime( DateTime->new( year => 1971 );
362 | $parser->set_coordinate( 12.345, 45.678 );
363 |
364 | $result = $parser->evaluate("time > 06:15 and sunrise > time");
365 |
366 | =head1 DESCRIPTION
367 |
368 | This module will parse a clear text time expression and evaluate if it is
369 | true or not.
370 |
371 | Expressions are written in clear text and may contain operators, variables and
372 | time expressions.
373 |
374 | =head2 Operators
375 |
376 | Valid operators are:
377 |
378 | =over
379 |
380 | =item B<+>
381 |
382 | Add two times
383 |
384 | =item B<->
385 |
386 | Subtracts a time from another
387 |
388 | =item B>
389 |
390 | Verify left time expression is less than right expression.
391 |
392 | =item B>
393 |
394 | Verify left time expression is greater than right expression.
395 |
396 | =item B
397 |
398 | Left and right expression must be true
399 |
400 | =item B
401 |
402 | Left, right or both expressions must be true.
403 |
404 | =back
405 |
406 | =head2 Variables
407 |
408 | Valid variables are:
409 |
410 | =over
411 |
412 | =item B