File Coverage

blib/lib/DateTime/Format/EraLegis.pm
Criterion Covered Total %
statement 168 185 90.8
branch 56 94 59.5
condition 9 15 60.0
subroutine 31 34 91.1
pod 0 3 0.0
total 264 331 79.7


line stmt bran cond sub pod time code
1             package DateTime::Format::EraLegis;
2             $DateTime::Format::EraLegis::VERSION = '0.013';
3             # ABSTRACT: DateTime formatter for Era Legis (https://eralegis.info)
4              
5 1     1   1065692 use strict;
  1         5  
  1         44  
6 1     1   7 use warnings;
  1         2  
  1         69  
7 1     1   1019 use Function::Parameters qw(fun method);
  1         6483  
  1         6  
8 1     1   1371 use Moo;
  1         9613  
  1         8  
9 1     1   2917 use strictures 2;
  1         2337  
  1         70  
10 1     1   581 use namespace::clean;
  1         7  
  1         11  
11 1     1   1240 use Astro::Sunrise qw(sunrise);
  1         20885  
  1         178  
12              
13             has 'ephem' => (
14             is => 'ro',
15             # isa => 'DateTime::Format::EraLegis::Ephem',
16             lazy => 1,
17             builder => 1,
18             );
19              
20             has 'style' => (
21             is => 'ro',
22             # isa => 'DateTime::Format::EraLegis::Style',
23             lazy => 1,
24             builder => 1,
25             );
26              
27 0 0   0   0 method _build_ephem() {
  0 0       0  
  0         0  
  0         0  
28 0         0 return DateTime::Format::EraLegis::Ephem::DBI->new;
29             }
30              
31 0 0   0   0 method _build_style() {
  0 0       0  
  0         0  
  0         0  
32 0         0 return DateTime::Format::EraLegis::Style->new;
33             }
34              
35 22 50 66 22 0 64608 method format_datetime($dt, :$format = 'plain', :$geo = undef) {
  22 50       133  
  22 100       39  
  22 50       84  
  22         84  
  22         51  
  22         60  
  22         35  
36 22         83 $dt = $dt->clone;
37              
38             ### Day of week should match existing time zone
39 22         383 my $dow = $dt->day_of_week;
40              
41             ### Adjust $dow based on sunrise time
42 22         166 my $sunrise = '06:00:00'; # default to 6am sunrise
43 22 100       67 if ($geo) {
44 2         8 ($sunrise, undef) = sunrise(
45             { year => $dt->year
46             , month => $dt->month
47             , day => $dt->day
48             , lat => $geo->[0]
49             , lon => $geo->[1]
50             , tz => ($dt->offset / 3600)
51             , precise => 1
52             }
53             );
54 2         2536 $sunrise .= ':00';
55             }
56 22 100       112 if ($dt->hms lt $sunrise) {
57 14 100       246 $dow = $dow == 1 ? 7 : $dow - 1;
58             }
59              
60 22         176 my %tdate = (
61             evdate_local => $dt->ymd . ' ' . $dt->hms,
62             dow => $dow,
63             sunrise => $sunrise,
64             is_dst => $dt->is_dst,
65             tz_offset => $dt->offset,
66             );
67              
68             ### But pull ephemeris data based on UTC
69 22         3656 $dt->set_time_zone('UTC');
70 22         6122 $tdate{evdate_utc} = $dt->ymd . ' ' . $dt->hms;
71              
72 22         612 for ( qw(sol luna) ) {
73 44         1645 my $deg = $self->ephem->lookup( $_, $dt );
74 44         248 $tdate{$_}{sign} = int($deg / 30);
75 44         175 $tdate{$_}{deg} = int($deg % 30);
76             }
77              
78             my $years = $dt->year -
79 22 100 100     101 (($dt->month <= 3 && $tdate{sol}{sign} > 0) ? 1905 : 1904);
80 22         346 $tdate{year} = [ int( $years/22 ), int( $years%22 ) ];
81              
82 22         816 $tdate{plain} = $self->style->express( \%tdate );
83              
84 22 100       311 return ($format eq 'raw') ? \%tdate : $tdate{plain};
85             }
86              
87             ######################################################
88             package DateTime::Format::EraLegis::Ephem;
89 1     1   924 use Moo::Role;
  1         2781  
  1         7  
90 1     1   372 use strictures 2;
  1         6  
  1         32  
91              
92             requires 'lookup';
93              
94             ######################################################
95             package DateTime::Format::EraLegis::Ephem::DBI;
96             $DateTime::Format::EraLegis::Ephem::DBI::VERSION = '0.013';
97 1     1   329 use 5.010;
  1         3  
98 1     1   3 use Function::Parameters qw(fun method);
  1         2  
  1         5  
99 1     1   443 use Moo;
  1         1  
  1         5  
100 1     1   275 use strictures 2;
  1         7  
  1         26  
101 1     1   268 use namespace::clean;
  1         2  
  1         8  
102 1     1   227 use Carp;
  1         1  
  1         47  
103 1     1   4 use DBI;
  1         2  
  1         115  
104              
105             with 'DateTime::Format::EraLegis::Ephem';
106              
107             has 'ephem_db' => (
108             is => 'ro',
109             # isa => 'Str',
110             builder => 1,
111             lazy => 1,
112             );
113              
114             has 'dbh' => (
115             is => 'ro',
116             # isa => 'DBI::db',
117             builder => 1,
118             lazy => 1,
119             );
120              
121 0 0   0   0 method _build_ephem_db() {
  0 0       0  
  0         0  
  0         0  
122             return $ENV{ERALEGIS_EPHEMDB}
123 0   0     0 // croak 'No ephemeris database defined';
124             }
125              
126 1 50   1   14 method _build_dbh() {
  1 50       4  
  1         3  
  1         2  
127 1         27 return DBI->connect( 'dbi:SQLite:dbname='.$self->ephem_db );
128             }
129              
130 44 50   44 0 592 method lookup($body, $dt) {
  44 50       101  
  44         96  
  44         105  
  44         72  
131 44         187 my $time = $dt->ymd . ' ' . $dt->hms;
132 44 50       1164 croak 'Date is before era legis' if $time lt '1904-03-20';
133 44         1316 my $rows = $self->dbh->selectcol_arrayref(
134             q{SELECT degree FROM ephem
135             WHERE body = ? AND time < ?
136             ORDER BY time DESC LIMIT 1},
137             undef, $body, $time );
138 44 50       18401 croak "Cannot find date entry for $time." unless $rows;
139              
140 44         200 return $rows->[0];
141             }
142              
143             ######################################################
144              
145             package DateTime::Format::EraLegis::Style;
146              
147 1     1   303 use 5.010;
  1         4  
148 1     1   4 use Function::Parameters qw(fun method);
  1         1  
  1         2  
149 1     1   409 use Moo;
  1         2  
  1         3  
150 1     1   239 use strictures 2;
  1         3  
  1         26  
151 1     1   290 use namespace::clean;
  1         1  
  1         4  
152 1     1   169 use utf8;
  1         2  
  1         6  
153 1     1   479 use Roman::Unicode qw(to_roman);
  1         32171  
  1         218  
154              
155             has 'lang' => (
156             is => 'ro',
157             # isa => 'Str',
158             default => 'latin',
159             required => 1,
160             );
161              
162             has 'dow' => (
163             is => 'ro',
164             # isa => 'ArrayRef',
165             builder => 1,
166             lazy => 1,
167             );
168              
169             has 'signs' => (
170             is => 'ro',
171             # isa => 'ArrayRef',
172             builder => 1,
173             lazy => 1,
174             );
175              
176             has 'years' => (
177             is => 'ro',
178             # isa => 'ArrayRef',
179             builder => 1,
180             lazy => 1,
181             );
182              
183             has 'show_terse' => (
184             is => 'ro',
185             # isa => 'Bool',
186             default => 0,
187             );
188              
189             has [ qw( show_deg show_dow show_year roman_year ) ] => (
190             is => 'ro',
191             # isa => 'Bool',
192             default => 1,
193             );
194              
195             has 'template' => (
196             is => 'ro',
197             # isa => 'Str',
198             builder => 1,
199             lazy => 1,
200             );
201              
202             has 'vs15' => (
203             is => 'ro',
204             # isa => 'Bool',
205             default => 1,
206             );
207              
208 3 50   3   36 method _build_dow() {
  3 50       9  
  3         21  
  3         4  
209             return
210 3 50       46 ($self->lang eq 'symbol')
    100          
211             ? [qw( ☉ ☽ ♂ ☿ ♃ ♀ ♄ ☉ )]
212             : ($self->lang eq 'english')
213             ? [qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday)]
214             : [qw(Solis Lunae Martis Mercurii Iovis Veneris Saturni Solis)];
215             }
216              
217 5 50   5   194882 method _build_signs() {
  5 50       19  
  5         11  
  5         9  
218 5 100       58 return [qw( ♈ ♉ ♊ ♋ ♌ ♍ ♎ ♏ ♐ ♑ ♒ ♓ )]
219             if $self->lang eq 'symbol';
220              
221 2 50       11 return [qw(Aries Taurus Gemini Cancer Leo Virgo Libra Scorpio Sagittarius Capricorn Aquarius Pisces)]
222             if $self->lang eq 'english';
223              
224 2 50       10 return [qw(Aries Taurus Gemini Cancer Leo Virgo Libra Scorpio Sagittarius Capricorn Aquarius Pisces)]
225             if $self->lang eq 'poor-latin';
226              
227 2 50 33     70 return [qw(Arietis Tauri Geminorum Cancri Leonis Virginis Librae Scorpii Sagittarii Capricorni Aquarii Piscis)]
228             if $self->lang eq 'latin' && $self->show_deg;
229              
230 0         0 return [qw(Ariete Tauro Geminis Cancro Leone Virginie Libra Scorpio Sagittario Capricorno Aquario Pisci)];
231             }
232              
233 4 50   4   62 method _build_years() {
  4 50       11  
  4         8  
  4         6  
234             return
235             ($self->roman_year)
236 4 50       23 ? [ 0, map { to_roman($_) } 1..21 ]
  84         12434  
237             : [ 0..21 ];
238             }
239              
240 5 50   5   71 method _build_template() {
  5 50       36  
  5         9  
  5         8  
241 5         11 my $template = '';
242 5 100       23 my $vs = $self->vs15 ? '︎' : '';
243 5 50       17 if ($self->show_deg) {
244 5         13 $template = "☉$vs in {sdeg}° {ssign} : ☽$vs in {ldeg}° {lsign}";
245             }
246             else {
247 0         0 $template = "☉$vs in {ssign} : ☽$vs in {lsign}";
248             }
249 5 100       18 if ($self->show_terse) {
250 2         18 $template =~ s/ in / /g;
251             }
252 5 100       32 if ($self->show_dow) {
253 3         8 $template .= ' : ';
254 3 100       14 $template .= ($self->lang eq 'latin')
255             ? 'dies '
256             : '';
257 3         7 $template .= '{dow}';
258             }
259 5 100       16 if ($self->show_year) {
260 4         10 $template .= ' : ';
261 4 50       55 $template .= ($self->lang eq 'symbol')
    100          
262             ? '{year1}{year2}'
263             : ($self->lang eq 'english')
264             ? 'Year {year1}.{year2} of the New Aeon'
265             : 'Anno {year1}{year2} æræ legis';
266             }
267              
268 5         23 return $template;
269             }
270              
271 22 50   22 0 279 method express( $tdate ) {
  22 50       62  
  22         48  
  22         52  
  22         33  
272 22         682 my $datestr = $self->template;
273              
274 22 100 100     282 my $vs = $self->lang eq 'symbol' && $self->vs15 ? '︎' : '';
275              
276 22         120 $datestr =~ s/{sdeg}/$tdate->{sol}{deg}/ge;
  22         159  
277 22         87 $datestr =~ s/{ssign}/$self->signs->[$tdate->{sol}{sign}].$vs/ge;
  22         581  
278 22         301 $datestr =~ s/{ldeg}/$tdate->{luna}{deg}/ge;
  22         93  
279 22         67 $datestr =~ s/{lsign}/$self->signs->[$tdate->{luna}{sign}].$vs/ge;
  22         557  
280 22         251 $datestr =~ s/{dow}/$self->dow->[$tdate->{dow}].$vs/ge;
  3         72  
281 22         69 $datestr =~ s/{year1}/$self->years->[$tdate->{year}[0]]/ge;
  21         516  
282 22         912 $datestr =~ s/{year2}/lc($self->years->[$tdate->{year}[1]])/ge;
  21         581  
283              
284 22         363 return $datestr;
285             }
286              
287             1;
288              
289             __END__
290              
291             =head1 NAME
292              
293             DateTime::Format::EraLegis - DateTime converter for Era Legis
294             DateTime::Format::EraLegis::Ephem - planetary ephemeris role
295             DateTime::Format::EraLegis::Ephem::DBI - default ephemeris getter
296             DateTime::Format::EraLegis::Style - customize output styles
297              
298             =head1 SYNOPSIS
299              
300             use DateTime::Format::EraLegis;
301              
302             my $ephem = DateTime::Format::EraLegis::Ephem::DBI->new(
303             ephem_db => 'db.sqlite3');
304             my $style = DateTime::Format::EraLegis::Style->new(
305             show_terse => 1, lang => 'symbol');
306             my $dtf = DateTime::Format::EraLegis->new(
307             ephem => $ephem, style => $style);
308              
309             my $dt->set_formatter($dtf);
310              
311             =head1 DESCRIPTION
312              
313             These three modules combined enable DateTime objects to emit date strings
314             formatted according to the Thelemic calendar. The ephemeris provides access
315             to the planetary location of the Sun and Moon keyed by UTC timestamp. The
316             style dictates the specific expression of the of datetime value using a
317             template into which one can place tokens which can be converted into the
318             sign/degree coordinates for the given date. A default style exists and is
319             permutable by boolean attributes.
320              
321             All three classes are built with Moose and behave accordingly. Method
322             arguments are typechecked and will die on failure. Defaults exist for
323             all attributes. All attributes are read-only and must be assigned at
324             the time of instantiation.
325              
326             =head1 ATTRIBUTES AND METHODS
327              
328             =over
329              
330             =item *
331              
332             DateTime::Format::EraLegis
333              
334             =over
335              
336             =item *
337              
338             ephem: DT::F::EL::Ephem object. Creates a new DBI one by default.
339              
340             =item *
341              
342             style: DT::F::EL::Style object. Creates a new one by default.
343              
344             =item *
345              
346             format_datetime(DateTime $dt, Str $format): Standard interface for a
347             DateTime::Format package. $format is one of 'plain' or 'raw'.
348             Defaults to 'plain'.
349              
350             =back
351              
352             =item *
353              
354             DateTime::Format::EraLegis::Ephem (Role)
355              
356             =over
357              
358             =item *
359              
360             lookup(Str $body, DateTime $dt): Required by any role consumer. $body
361             is one of "sol" or "luna". $dt is the date in question (in UTC!).
362             Returns the number of degrees away from 0 degrees Aries. Divide by
363             thirty to get the sign. Modulo by thirty to get the degrees of that
364             sign.
365              
366             =back
367              
368             =item *
369              
370             DateTime::Format::EraLegis::Ephem::DBI
371              
372             =over
373              
374             =item *
375              
376             Consumes DT::F::EL::Ephem role.
377              
378             =item *
379              
380             ephem_db: Filename of the sqlite3 ephemeris database. Defaults to the value
381             of $ENV{ERALEGIS_EPHEMDB}.
382              
383             =item *
384              
385             dbh: DBI handle for ephemeris database. Defaults to creating a new one pointing
386             to the ephem_db database.
387              
388             =back
389              
390             =item *
391              
392             DateTime::Format::EraLegis::Style
393              
394             =over
395              
396             =item *
397              
398             template: Assign a custom template value. Variables (enclosed in '{}')
399             include 'ssign' and 'sdeg' for Sol sign and degree, 'lsign' and 'ldeg'
400             for Luna sign and degree, 'dow' for day of the week, and 'year1' and
401             'year2' for the two docosades. Example:
402              
403             "Sol in {sdeg} degrees {ssign}, anno {year1}{year2} era legis"
404              
405             Interpolated values get assigned based on the setting of 'lang'.
406              
407             =item *
408              
409             lang: Set the output language, one of latin, english, symbol, poor-latin.
410             Defaults to 'latin'.
411              
412             =item *
413              
414             show_terse, show_deg, show_dow, show_year, roman_year: Flags to direct
415             the style to alter the default template.
416              
417             =back
418              
419             =back
420              
421             =head1 DATABASE SCHEMA
422              
423             The schema for the DBI ephemeris table is very simple and the querying
424             SQL very generic. Most DBI backends should work without issue, though
425             SQLite3 is the only one tested. The schema is:
426              
427             CREATE TABLE ephem (
428             body TEXT, -- one of 'sol' or 'luna'
429             time DATETIME, -- UTC timestamp of shift into degree
430             degree INTEGER NOT NULL, -- degrees from 0 degrees Aries
431             PRIMARY KEY (body, time)
432             );
433              
434             =head1 BUGS
435              
436             Please report bugs as issues at:
437             https://gitlab.com/clayfouts/datetime-format-eralegis
438              
439             =head1 AUTHOR
440              
441             Clay Fouts <cfouts@khephera.net>
442              
443             =head1 COPYRIGHT & LICENSE
444              
445             Copyright (c) 2012 Clay Fouts
446              
447             This is free software; you can redistribute it and/or modify it under
448             the same terms as the Perl 5 programming language system itself.
449              
450             =cut