File Coverage

blib/lib/DateTime/Format/EraLegis.pm
Criterion Covered Total %
statement 163 174 93.6
branch 59 92 64.1
condition 15 27 55.5
subroutine 38 41 92.6
pod n/a
total 275 334 82.3


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