| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package DateTimeX::Format::Excel; |
|
2
|
|
|
|
|
|
|
our $AUTHORITY = 'cpan:JANDREW'; |
|
3
|
3
|
|
|
3
|
|
298734
|
use version 0.77; our $VERSION = version->declare("v0.12.48"); |
|
|
3
|
|
|
|
|
3758
|
|
|
|
3
|
|
|
|
|
16
|
|
|
4
|
3
|
|
|
3
|
|
221
|
use 5.010; |
|
|
3
|
|
|
|
|
10
|
|
|
5
|
3
|
|
|
3
|
|
10
|
use strict; |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
45
|
|
|
6
|
3
|
|
|
3
|
|
9
|
use warnings; |
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
71
|
|
|
7
|
3
|
|
|
3
|
|
8
|
use List::Util 1.33; |
|
|
3
|
|
|
|
|
52
|
|
|
|
3
|
|
|
|
|
136
|
|
|
8
|
3
|
|
|
3
|
|
1402
|
use Moose 2.1213; |
|
|
3
|
|
|
|
|
802912
|
|
|
|
3
|
|
|
|
|
20
|
|
|
9
|
3
|
|
|
3
|
|
15051
|
use MooseX::StrictConstructor; |
|
|
3
|
|
|
|
|
39843
|
|
|
|
3
|
|
|
|
|
9
|
|
|
10
|
3
|
|
|
3
|
|
17978
|
use MooseX::HasDefaults::RO; |
|
|
3
|
|
|
|
|
13571
|
|
|
|
3
|
|
|
|
|
9
|
|
|
11
|
3
|
|
|
3
|
|
18833
|
use DateTime; |
|
|
3
|
|
|
|
|
128942
|
|
|
|
3
|
|
|
|
|
95
|
|
|
12
|
3
|
|
|
3
|
|
31
|
use Carp qw( cluck ); |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
152
|
|
|
13
|
3
|
|
|
3
|
|
1452
|
use Types::Standard -types; |
|
|
3
|
|
|
|
|
136996
|
|
|
|
3
|
|
|
|
|
28
|
|
|
14
|
|
|
|
|
|
|
if( $ENV{ Smart_Comments } ){ |
|
15
|
3
|
|
|
3
|
|
8101
|
use Smart::Comments -ENV; |
|
|
3
|
|
|
|
|
23148
|
|
|
|
3
|
|
|
|
|
23
|
|
|
16
|
|
|
|
|
|
|
### Smart-Comments turned on for DateTimeX-Format-Excel ... |
|
17
|
|
|
|
|
|
|
} |
|
18
|
3
|
|
|
3
|
|
2196
|
use lib '../../../lib',; |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
16
|
|
|
19
|
3
|
|
|
|
|
36
|
use DateTimeX::Format::Excel::Types 0.012 qw( |
|
20
|
|
|
|
|
|
|
DateTimeHash |
|
21
|
|
|
|
|
|
|
DateTimeInstance |
|
22
|
|
|
|
|
|
|
HashToDateTime |
|
23
|
|
|
|
|
|
|
is_ExcelEpoch |
|
24
|
|
|
|
|
|
|
ExcelEpoch |
|
25
|
|
|
|
|
|
|
SystemName |
|
26
|
3
|
|
|
3
|
|
1443
|
); |
|
|
3
|
|
|
|
|
56
|
|
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
#########1 Dispatch Tables 3#########4#########5#########6#########7#########8#########9 |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
my $input_scrub ={ |
|
31
|
|
|
|
|
|
|
win_excel =>{ |
|
32
|
|
|
|
|
|
|
action => sub{ |
|
33
|
|
|
|
|
|
|
### <where> - Reached win_excel scrub with: @_ |
|
34
|
|
|
|
|
|
|
if(int( $_[0] ) == 60){ |
|
35
|
|
|
|
|
|
|
cluck "-1900-February-29- is not a real date (contrary to excel implementation)"; |
|
36
|
|
|
|
|
|
|
}elsif($_[0] == 0){ |
|
37
|
|
|
|
|
|
|
cluck "-1900-January-0- is not a real date (contrary to excel implementation)"; |
|
38
|
|
|
|
|
|
|
} |
|
39
|
|
|
|
|
|
|
### <where> - Finished testing Lotus 123 date error warnings ... |
|
40
|
|
|
|
|
|
|
my $return =( |
|
41
|
|
|
|
|
|
|
(int( $_[0] ) > 60) ? ($_[0] - 1) : |
|
42
|
|
|
|
|
|
|
(int( $_[0] ) == 0) ? ($_[0] + 1) : $_[0] ); |
|
43
|
|
|
|
|
|
|
### <where> - updated date: $return |
|
44
|
|
|
|
|
|
|
return $return; |
|
45
|
|
|
|
|
|
|
}, |
|
46
|
|
|
|
|
|
|
output => sub{ |
|
47
|
|
|
|
|
|
|
### <where> - Reached win_excel output with: @_ |
|
48
|
|
|
|
|
|
|
my $return =( (defined( $_[0] ) and int( $_[0] ) > 59) ? ($_[0] + 1) : $_[0] ); |
|
49
|
|
|
|
|
|
|
if( defined( $return ) and $return < 1 ){ |
|
50
|
|
|
|
|
|
|
$return = undef; |
|
51
|
|
|
|
|
|
|
} |
|
52
|
|
|
|
|
|
|
### <where> - updated date: $return |
|
53
|
|
|
|
|
|
|
return $return; |
|
54
|
|
|
|
|
|
|
}, |
|
55
|
|
|
|
|
|
|
date_time_hash =>{ |
|
56
|
|
|
|
|
|
|
year => 1899,# actually 1900 |
|
57
|
|
|
|
|
|
|
month => 12,# actually 01 |
|
58
|
|
|
|
|
|
|
day => 31,# actually 00 |
|
59
|
|
|
|
|
|
|
}, |
|
60
|
|
|
|
|
|
|
}, |
|
61
|
|
|
|
|
|
|
apple_excel =>{ |
|
62
|
|
|
|
|
|
|
date_time_hash =>{ |
|
63
|
|
|
|
|
|
|
year => 1904, |
|
64
|
|
|
|
|
|
|
month => 1, |
|
65
|
|
|
|
|
|
|
day => 1, |
|
66
|
|
|
|
|
|
|
}, |
|
67
|
|
|
|
|
|
|
} |
|
68
|
|
|
|
|
|
|
}; |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
#########1 Public Attributes 3#########4#########5#########6#########7#########8#########9 |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
has system_type =>( |
|
73
|
|
|
|
|
|
|
isa => SystemName, |
|
74
|
|
|
|
|
|
|
reader => 'get_system_type', |
|
75
|
|
|
|
|
|
|
writer => 'set_system_type', |
|
76
|
|
|
|
|
|
|
default => 'win_excel', |
|
77
|
|
|
|
|
|
|
trigger => \&_set_system, |
|
78
|
|
|
|
|
|
|
); |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
#########1 Public Methods 3#########4#########5#########6#########7#########8#########9 |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
sub parse_datetime{ |
|
83
|
36
|
|
|
36
|
1
|
13161
|
my ( $self, $date_num, $timezone_flag, $timezone ) = @_; |
|
84
|
|
|
|
|
|
|
### <where> - Reached parse_datetime for: $date_num |
|
85
|
36
|
100
|
|
|
|
91
|
if( !is_ExcelEpoch( $date_num ) ){ |
|
86
|
|
|
|
|
|
|
### <where> - not and excel epoch: $date_num |
|
87
|
1
|
|
|
|
|
13
|
return $date_num; |
|
88
|
|
|
|
|
|
|
} |
|
89
|
|
|
|
|
|
|
### <where> - Passed the type constraint ... |
|
90
|
35
|
100
|
|
|
|
1329
|
if( my $action = $input_scrub->{$self->get_system_type}->{action} ){ |
|
91
|
|
|
|
|
|
|
### <where> - There is an action: $action |
|
92
|
|
|
|
|
|
|
### <where> - Using system name: $self->get_system_type |
|
93
|
28
|
|
|
|
|
54
|
$date_num = $action->( $date_num ); |
|
94
|
|
|
|
|
|
|
} |
|
95
|
|
|
|
|
|
|
### <where> - Updated date num: $date_num |
|
96
|
35
|
|
|
|
|
171
|
$date_num =~ /^ (\d+ (?: (\.\d+ ) )? ) $/x; |
|
97
|
35
|
|
|
|
|
57
|
my $excel_days = $1; |
|
98
|
35
|
|
|
|
|
36
|
my $excel_secs = $2; |
|
99
|
|
|
|
|
|
|
### <where> - Excel added days: $excel_days |
|
100
|
|
|
|
|
|
|
### <where> - Excel seconds: $excel_secs |
|
101
|
35
|
|
|
|
|
1048
|
my $dt = $self->_get_epoch_start->clone(); |
|
102
|
|
|
|
|
|
|
### <where> - DateTime: $dt |
|
103
|
35
|
100
|
|
|
|
315
|
if(defined $excel_secs){ |
|
104
|
7
|
|
|
|
|
19
|
$excel_secs = $excel_secs * (60*60*24);# Seconds in most days |
|
105
|
7
|
|
|
|
|
11
|
my $excel_nanoseconds = ($excel_secs - int($excel_secs)) * 1_000_000_000; |
|
106
|
|
|
|
|
|
|
### <where> - Excel days: $excel_days |
|
107
|
|
|
|
|
|
|
### <where> - Excel seconds: $excel_secs |
|
108
|
|
|
|
|
|
|
### <where> - Excel nano seconds: $excel_nanoseconds |
|
109
|
7
|
|
|
|
|
18
|
$dt->add( days => $excel_days, |
|
110
|
|
|
|
|
|
|
seconds => $excel_secs, |
|
111
|
|
|
|
|
|
|
nanoseconds => $excel_nanoseconds); |
|
112
|
|
|
|
|
|
|
} else { |
|
113
|
|
|
|
|
|
|
### <where> - No seconds in the epoch ... |
|
114
|
28
|
|
|
|
|
65
|
$dt->add( days => $excel_days ); |
|
115
|
|
|
|
|
|
|
} |
|
116
|
35
|
50
|
33
|
|
|
13870
|
if( $timezone_flag and $timezone_flag eq 'time_zone' ){ |
|
117
|
|
|
|
|
|
|
### <where> - Setting timezone to: $timezone |
|
118
|
0
|
|
|
|
|
0
|
$dt->set_time_zone( $timezone ); |
|
119
|
|
|
|
|
|
|
}; |
|
120
|
|
|
|
|
|
|
### <where> - DateTime: $dt |
|
121
|
|
|
|
|
|
|
|
|
122
|
35
|
|
|
|
|
128
|
return $dt; |
|
123
|
|
|
|
|
|
|
} |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub format_datetime{ |
|
126
|
156
|
|
|
156
|
1
|
104981
|
my ( $self, $date_time ) = @_; |
|
127
|
|
|
|
|
|
|
### <where> - Reached format_datetime with: $date_time |
|
128
|
156
|
|
|
|
|
338
|
DateTimeInstance->( $date_time ); |
|
129
|
156
|
|
|
|
|
8692
|
my $base = $self->_get_epoch_start->clone(); |
|
130
|
156
|
|
|
|
|
1381
|
my $test = DateTime->compare_ignore_floating( $date_time, $base ); |
|
131
|
|
|
|
|
|
|
### <where> - DateTime base is: $base |
|
132
|
|
|
|
|
|
|
### <where> - Using system name: $self->get_system_type |
|
133
|
|
|
|
|
|
|
### <where> - Test result: $test |
|
134
|
156
|
|
|
|
|
3935
|
my $excel = undef; |
|
135
|
156
|
|
|
|
|
132
|
my $return_string = 0; |
|
136
|
156
|
100
|
|
|
|
230
|
if( $test < 0 ){ |
|
137
|
3
|
|
|
|
|
5
|
$return_string = 1; |
|
138
|
|
|
|
|
|
|
}else{ |
|
139
|
153
|
|
|
|
|
278
|
$excel = $date_time->jd - $base->jd; |
|
140
|
|
|
|
|
|
|
} |
|
141
|
|
|
|
|
|
|
### <where> - Initial excel epoch: $excel |
|
142
|
156
|
100
|
100
|
|
|
6724
|
if( defined $excel and my $action = $input_scrub->{$self->get_system_type}->{output} ){ |
|
143
|
|
|
|
|
|
|
### <where> - There is an action: $action |
|
144
|
|
|
|
|
|
|
### <where> - For: $self->get_system_type |
|
145
|
85
|
|
|
|
|
133
|
$excel = $action->( $excel ); |
|
146
|
85
|
100
|
|
|
|
137
|
$return_string = 1 if !defined $excel; |
|
147
|
|
|
|
|
|
|
} |
|
148
|
|
|
|
|
|
|
### <where> - Should return a date string: $return_string |
|
149
|
|
|
|
|
|
|
### <where> - Final excel epoch: $excel |
|
150
|
|
|
|
|
|
|
### <where> - Original DateTime: $date_time |
|
151
|
|
|
|
|
|
|
|
|
152
|
156
|
100
|
|
|
|
897
|
return ( $return_string ) ? $date_time : $excel; |
|
153
|
|
|
|
|
|
|
} |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
#########1 Private Attributes 3#########4#########5#########6#########7#########8#########9 |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
has _epoch_start =>( |
|
158
|
|
|
|
|
|
|
isa => DateTimeInstance->plus_coercions( HashToDateTime ), |
|
159
|
|
|
|
|
|
|
writer => '_set_epoch_start', |
|
160
|
|
|
|
|
|
|
reader => '_get_epoch_start', |
|
161
|
|
|
|
|
|
|
coerce => 1, |
|
162
|
|
|
|
|
|
|
default => sub{ DateTime->new( |
|
163
|
|
|
|
|
|
|
$input_scrub->{win_excel}->{date_time_hash} |
|
164
|
|
|
|
|
|
|
) }, |
|
165
|
|
|
|
|
|
|
); |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
#########1 Private Methods 3#########4#########5#########6#########7#########8#########9 |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
sub _set_system{ |
|
170
|
4
|
|
|
4
|
|
6
|
my ( $self, $system_type ) = @_; |
|
171
|
4
|
|
|
|
|
128
|
$self->_set_epoch_start( $input_scrub->{$system_type}->{date_time_hash} ); |
|
172
|
4
|
|
|
|
|
24
|
return $system_type; |
|
173
|
|
|
|
|
|
|
} |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
#########1 Phinish 3#########4#########5#########6#########7#########8#########9 |
|
176
|
|
|
|
|
|
|
|
|
177
|
3
|
|
|
3
|
|
3150
|
no Moose; |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
19
|
|
|
178
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable( |
|
179
|
|
|
|
|
|
|
inline_constructor => 0, |
|
180
|
|
|
|
|
|
|
); |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
1; |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
#########1 Documentation 3#########4#########5#########6#########7#########8#########9 |
|
185
|
|
|
|
|
|
|
__END__ |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
=head1 NAME |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
DateTimeX::Format::Excel - Microsofty conversion of Excel epochs |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=begin html |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
<a href="https://www.perl.org"> |
|
194
|
|
|
|
|
|
|
<img src="https://img.shields.io/badge/perl-5.10+-brightgreen.svg" alt="perl version"> |
|
195
|
|
|
|
|
|
|
</a> |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
<a href="https://travis-ci.org/jandrew/DateTimeX-Format-Excel"> |
|
198
|
|
|
|
|
|
|
<img alt="Build Status" src="https://travis-ci.org/jandrew/DateTimeX-Format-Excel.png?branch=master" alt='Travis Build'/> |
|
199
|
|
|
|
|
|
|
</a> |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
<a href='https://coveralls.io/r/jandrew/DateTimeX-Format-Excel?branch=master'> |
|
202
|
|
|
|
|
|
|
<img src='https://coveralls.io/repos/jandrew/DateTimeX-Format-Excel/badge.svg?branch=master' alt='Coverage Status' /> |
|
203
|
|
|
|
|
|
|
</a> |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
<a href='https://github.com/jandrew/DateTimeX-Format-Excel'> |
|
206
|
|
|
|
|
|
|
<img src="https://img.shields.io/github/tag/jandrew/DateTimeX-Format-Excel.svg?label=github level" alt="github level"/> |
|
207
|
|
|
|
|
|
|
</a> |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
<a href="https://metacpan.org/pod/DateTimeX::Format::Excel"> |
|
210
|
|
|
|
|
|
|
<img src="https://badge.fury.io/pl/DateTimeX-Format-Excel.svg?label=cpan version" alt="CPAN version" height="20"> |
|
211
|
|
|
|
|
|
|
</a> |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
<a href='http://cpants.cpanauthors.org/dist/DateTimeX-Format-Excel'> |
|
214
|
|
|
|
|
|
|
<img src='http://cpants.cpanauthors.org/dist/DateTimeX-Format-Excel.png' alt='kwalitee' height="20"/> |
|
215
|
|
|
|
|
|
|
</a> |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=end html |
|
218
|
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
#!/usr/bin/env perl |
|
222
|
|
|
|
|
|
|
use DateTimeX::Format::Excel; |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# From an Excel date number |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
my $parser = DateTimeX::Format::Excel->new(); |
|
227
|
|
|
|
|
|
|
print $parser->parse_datetime( 25569 )->ymd ."\n"; |
|
228
|
|
|
|
|
|
|
my $datetime = $parser->parse_datetime( 37680 ); |
|
229
|
|
|
|
|
|
|
print $datetime->ymd() ."\n"; |
|
230
|
|
|
|
|
|
|
$datetime = $parser->parse_datetime( 40123.625 ); |
|
231
|
|
|
|
|
|
|
print $datetime->iso8601() ."\n"; |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
# And back to an Excel number from a DateTime object |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
use DateTime; |
|
236
|
|
|
|
|
|
|
my $dt = DateTime->new( year => 1979, month => 7, day => 16 ); |
|
237
|
|
|
|
|
|
|
my $daynum = $parser->format_datetime( $dt ); |
|
238
|
|
|
|
|
|
|
print $daynum ."\n"; |
|
239
|
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
my $dt_with_time = DateTime->new( year => 2010, month => 7, day => 23 |
|
241
|
|
|
|
|
|
|
, hour => 18, minute => 20 ); |
|
242
|
|
|
|
|
|
|
my $parser_date = $parser->format_datetime( $dt_with_time ); |
|
243
|
|
|
|
|
|
|
print $parser_date ."\n"; |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
########################### |
|
246
|
|
|
|
|
|
|
# SYNOPSIS Screen Output |
|
247
|
|
|
|
|
|
|
# 01: 1970-01-01 |
|
248
|
|
|
|
|
|
|
# 02: 2003-02-28 |
|
249
|
|
|
|
|
|
|
# 03: 2009-11-06T15:00:00 |
|
250
|
|
|
|
|
|
|
# 04: 29052 |
|
251
|
|
|
|
|
|
|
# 05: 40382.763888889 |
|
252
|
|
|
|
|
|
|
########################### |
|
253
|
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
Excel uses a different system for its dates than most Unix programs. |
|
257
|
|
|
|
|
|
|
This package allows you to convert between the Excel raw format and |
|
258
|
|
|
|
|
|
|
and L<DateTime> objects, which can then be further converted via any |
|
259
|
|
|
|
|
|
|
of the other L<DateTime::Format::* |
|
260
|
|
|
|
|
|
|
|https://metacpan.org/search?q=DateTime%3A%3AFormat> modules, or just |
|
261
|
|
|
|
|
|
|
with L<DateTime>'s methods. The L<DateTime::Format::Excel> module states |
|
262
|
|
|
|
|
|
|
"we assume what Psion assumed for their Abacus / Sheet program". As a |
|
263
|
|
|
|
|
|
|
consequence the output does not follow exactly the output of Excel. |
|
264
|
|
|
|
|
|
|
Especially in the Windows range of 0-60. This module attempts to more |
|
265
|
|
|
|
|
|
|
faithfully follow actual Microsoft Excel with a few notable exceptions. |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
Excel has a few date quirks. First, it allows two different epochs. One |
|
268
|
|
|
|
|
|
|
for the Windows world and one for the Apple world. The windows epoch |
|
269
|
|
|
|
|
|
|
starts in 0-January-1900 and allows for 29-February-1900 (both non real |
|
270
|
|
|
|
|
|
|
dates). Most of the explanations for the difference between windows |
|
271
|
|
|
|
|
|
|
implementations and Apple implementations focus on the fact that there |
|
272
|
|
|
|
|
|
|
was no leap year in 1900 L<(the Gregorian vs Julian calendars) |
|
273
|
|
|
|
|
|
|
|http://en.wikipedia.org/wiki/Gregorian_calendar> and the Apple |
|
274
|
|
|
|
|
|
|
version wanted to skip that issue. Both non real dates appear to have |
|
275
|
|
|
|
|
|
|
been a known issue in the original design of VisiCalc that was carried |
|
276
|
|
|
|
|
|
|
through Lotus 1-2-3 and into Excel for L<compatibility |
|
277
|
|
|
|
|
|
|
|http://support.microsoft.com/kb/214326>. (Spreadsheets were arguably the |
|
278
|
|
|
|
|
|
|
first personal computer killer app and Excel was a L<johnny come lately |
|
279
|
|
|
|
|
|
|
|http://en.wikipedia.org/wiki/Lotus_1-2-3#VisiCalc> trying to gain an entry |
|
280
|
|
|
|
|
|
|
into the market at the time.) The closest microsoft discussion I could find |
|
281
|
|
|
|
|
|
|
on this issue is L<here|http://www.joelonsoftware.com/items/2006/06/16.html>. |
|
282
|
|
|
|
|
|
|
In any case the apple version starts 1-January-1904. (counting from 0 while |
|
283
|
|
|
|
|
|
|
also avoiding the leap year issue). In both cases the Windows and Apple |
|
284
|
|
|
|
|
|
|
version use integers from the epoch start to represent days and the decimal |
|
285
|
|
|
|
|
|
|
portion to represent a portion of a day. Both Windows and Apple Excel will |
|
286
|
|
|
|
|
|
|
attempt to convert recognized date strings to an Excel epoch for storage with |
|
287
|
|
|
|
|
|
|
the exception that any date prior to the epoch start will be stored as a |
|
288
|
|
|
|
|
|
|
string. (31-December-1899 and earlier for Windows and 31-December-1903 and |
|
289
|
|
|
|
|
|
|
earlier for Apple). Next, Excel does not allow for a time zone component of |
|
290
|
|
|
|
|
|
|
each number. Finally, in the Windows version when dealing with epochs that |
|
291
|
|
|
|
|
|
|
do not have a date component just a time component all values will fall |
|
292
|
|
|
|
|
|
|
between 0 and 1 which is a non real date (0-January-1900). |
|
293
|
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
=head2 Caveat utilitor |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
This explanation is not intended to justify Microsofts decisions with Excel |
|
297
|
|
|
|
|
|
|
dates just replicate them as faithfully as possible. This module makes the |
|
298
|
|
|
|
|
|
|
assumption that you already know if your date is a string or a number in Excel |
|
299
|
|
|
|
|
|
|
and that you will handle string to DateTime conversions elsewhere. see |
|
300
|
|
|
|
|
|
|
L<DateTime::Format::Flexible>. Any passed strings will die. (As a failure |
|
301
|
|
|
|
|
|
|
of a L<Type::Tiny> test) This module also makes several unilateral decisions |
|
302
|
|
|
|
|
|
|
to deal with corner cases. When a 0 date is requested to be converted to |
|
303
|
|
|
|
|
|
|
DateTime it will use L<Carp> to cluck that it received a bad date and then |
|
304
|
|
|
|
|
|
|
provide a DateTime object dated 1-January-1900 (Excel would provide |
|
305
|
|
|
|
|
|
|
0-January-1900). If a value between 0 and 1 is requested to be converted to |
|
306
|
|
|
|
|
|
|
a DateTime object the module will B<NOT> cluck and provide an object dated |
|
307
|
|
|
|
|
|
|
1-January-1900 with the appropriate time component. All Apple times are provide |
|
308
|
|
|
|
|
|
|
as 1-January-1904. Any requested numerical conversion for Windows >= 60 and |
|
309
|
|
|
|
|
|
|
< 61 will cluck and provide a DateTime object dated 1-March-1900 (Excel would |
|
310
|
|
|
|
|
|
|
provide 29-Febrary-1900). All requests for conversion of negative numbers to |
|
311
|
|
|
|
|
|
|
DateTime objects will die . If a DateTime object is provided for conversion |
|
312
|
|
|
|
|
|
|
to the Excel value and it falls earlier than 1-January-1900 for Windows and |
|
313
|
|
|
|
|
|
|
1-January-1904 for Apple then the DateTime object itself will be returned. |
|
314
|
|
|
|
|
|
|
If you accept the output of that L<method|/format_datetime( $date_time )> |
|
315
|
|
|
|
|
|
|
as a scalar, DateTime will stringify itself and give you a text equivalent |
|
316
|
|
|
|
|
|
|
date. For time zones you can L<pass|/parse_datetime( @arg_list )> a time zone |
|
317
|
|
|
|
|
|
|
with the excel number for conversion to the DateTime object. In reverse, |
|
318
|
|
|
|
|
|
|
the conversion to Excel Epoch uses the L<-E<gt>jd |
|
319
|
|
|
|
|
|
|
|https://metacpan.org/pod/DateTime#dt-jd-dt-mjd> method for calculation so |
|
320
|
|
|
|
|
|
|
the time zone is stripped out. No clone or duration calculations are provided |
|
321
|
|
|
|
|
|
|
with this module. Finally this is a L<Moose> based module and does |
|
322
|
|
|
|
|
|
|
not provide a functional interface. I<(Moose would allow it I just chose not |
|
323
|
|
|
|
|
|
|
to for design purposes)>. |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
The Types module for this package uses L<Type::Tiny> which can, in the background, |
|
326
|
|
|
|
|
|
|
use L<Type::Tiny::XS>. While in general this is a good thing you will need to make |
|
327
|
|
|
|
|
|
|
sure that Type::Tiny::XS is version 0.010 or newer since the older ones didn't support |
|
328
|
|
|
|
|
|
|
the 'Optional' method. |
|
329
|
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
=head2 Attributes |
|
331
|
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
Data passed to new when creating an instance (parser). For modification of |
|
333
|
|
|
|
|
|
|
these attributes see the listed L</Methods> of the instance. |
|
334
|
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
=head3 system_type |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
=over |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
B<Definition:> This attribute identifies whether the translation will be done |
|
340
|
|
|
|
|
|
|
for Windows Excel => 'win_excel' or Apple Excel => 'apple_excel'. |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
B<Default> win_excel (0-January-1900T00:00:00 = 0, range includes 29-February-1900) |
|
343
|
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
B<Range> win_excel|apple_excel (1-January-1904T00:00:00 = 0) |
|
345
|
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
=back |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=head2 Methods |
|
349
|
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
These include methods to adjust attributes as well as providing methods to |
|
351
|
|
|
|
|
|
|
provide the conversion functionality of the module. |
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=head3 get_system_type |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=over |
|
356
|
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
B<Definition:> This is the way to see whether the conversion is Windows or Apple based |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
B<Accepts:>Nothing |
|
360
|
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
B<Returns:> win_excel|apple_excel |
|
362
|
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
=back |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
=head3 set_system_type( $system ) |
|
366
|
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
=over |
|
368
|
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
B<Definition:> This is the way to set the base epoch for the translator |
|
370
|
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
B<Accepts:> win_excel|apple_excel (see the L</DESCRIPTION> for details) |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
B<Returns:> Nothing |
|
374
|
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=back |
|
376
|
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=head3 parse_datetime( @arg_list ) |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
=over |
|
380
|
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
B<Definition:> This is how positive excel numbers are translated to L<DateTime> objects |
|
382
|
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
B<Accepts:> @arg_list - the order is important! |
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
=over |
|
386
|
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
B<0. > $the_excel_number_for_translation - must be positive - no strings allowed |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
B<1. > 'time_zone' (the only useful option - other values here will ignore position 2) |
|
390
|
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
B<2. > A recognizable time zone string or L<DateTime::TimeZone> object |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
B<example: > ( 12345, time_zone => 'America/Los_Angeles' ) |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=back |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
B<Returns:> A DateTime object set to match the passed values. A floating time zone is default. |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=back |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=head3 format_datetime( $date_time ) |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
=over |
|
404
|
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
B<Definition:> This is how DateTime objects can be translated to Excel epoch numbers |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
B<Accepts:> A L<DateTime> object |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
B<Returns:> An excel epoch number or DateTime object if it is before the relevant epoch start. |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
=back |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=head2 A note on text dates |
|
414
|
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
Dates saved in Excel prior to 1-January-1900 for Windows or 1-January-1904 for Apple are stored as text. |
|
416
|
|
|
|
|
|
|
I suggest using L<Type::Tiny::Manual::Coercions/Chained Coercions>. Or use an Excel reader |
|
417
|
|
|
|
|
|
|
that implements this for you like L<Spreadsheet::XLSX::Reader::LibXML> (self promotion). |
|
418
|
|
|
|
|
|
|
Here is one possible way to integrate text and dates in the same field into a consistent DateTime |
|
419
|
|
|
|
|
|
|
output. (I know it's a bit clunky but it's a place to start) |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
my $system_lookup = { |
|
422
|
|
|
|
|
|
|
'1900' => 'win_excel', |
|
423
|
|
|
|
|
|
|
'1904' => 'apple_excel', |
|
424
|
|
|
|
|
|
|
}; |
|
425
|
|
|
|
|
|
|
my @args_list = ( system_type => $system_lookup->{$workbook->get_epoch_year} ); |
|
426
|
|
|
|
|
|
|
my $converter = DateTimeX::Format::Excel->new( @args_list ); |
|
427
|
|
|
|
|
|
|
my $string_via = sub{ |
|
428
|
|
|
|
|
|
|
my $str = $_[0]; |
|
429
|
|
|
|
|
|
|
return DateTime::Format::Flexible->parse_datetime( $str ); |
|
430
|
|
|
|
|
|
|
}; |
|
431
|
|
|
|
|
|
|
my $num_via = sub{ |
|
432
|
|
|
|
|
|
|
my $num = $_[0]; |
|
433
|
|
|
|
|
|
|
return $converter->parse_datetime( $num ); |
|
434
|
|
|
|
|
|
|
}; |
|
435
|
|
|
|
|
|
|
my $date_time_from_value = Type::Coercion->new( |
|
436
|
|
|
|
|
|
|
type_coercion_map => [ Num, $num_via, Str, $string_via, ], |
|
437
|
|
|
|
|
|
|
); |
|
438
|
|
|
|
|
|
|
my $date_time_type = Type::Tiny->new( |
|
439
|
|
|
|
|
|
|
name => 'Custom_date_type', |
|
440
|
|
|
|
|
|
|
constraint => sub{ ref($_) eq 'DateTime' }, |
|
441
|
|
|
|
|
|
|
coercion => $date_time_from_value, |
|
442
|
|
|
|
|
|
|
); |
|
443
|
|
|
|
|
|
|
my $string_type = Type::Tiny->new( |
|
444
|
|
|
|
|
|
|
name => 'YYYYMMDD', |
|
445
|
|
|
|
|
|
|
constraint => sub{ |
|
446
|
|
|
|
|
|
|
!$_ or ( |
|
447
|
|
|
|
|
|
|
$_ =~ /^\d{4}\-(\d{2})-(\d{2})$/ and |
|
448
|
|
|
|
|
|
|
$1 > 0 and $1 < 13 and $2 > 0 and $2 < 32 ) |
|
449
|
|
|
|
|
|
|
}, |
|
450
|
|
|
|
|
|
|
coercion => Type::Coercion->new( |
|
451
|
|
|
|
|
|
|
type_coercion_map =>[ |
|
452
|
|
|
|
|
|
|
$date_time_type->coercibles, sub{ |
|
453
|
|
|
|
|
|
|
my $tmp = $date_time_type->coerce( $_ ); |
|
454
|
|
|
|
|
|
|
$tmp->format_cldr( 'yyyy-MM-dd' ) |
|
455
|
|
|
|
|
|
|
}, |
|
456
|
|
|
|
|
|
|
], |
|
457
|
|
|
|
|
|
|
), |
|
458
|
|
|
|
|
|
|
); |
|
459
|
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=head1 THANKS |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
Dave Rolsky (L<DROLSKY>) for kickstarting the DateTime project. |
|
464
|
|
|
|
|
|
|
Iain Truskett, Dave Rolsky, and Achim Bursian for maintaining L<DateTime::Format::Excel>. |
|
465
|
|
|
|
|
|
|
I used it heavily till I wrote this. |
|
466
|
|
|
|
|
|
|
Peter (Stig) Edwards and Bobby Metz for contributing to L<DateTime::Format::Excel>. |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=head1 Build/Install from Source |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
B<1.> Download a compressed file with the code |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
B<2.> Extract the code from the compressed file. If you are using tar this should work: |
|
473
|
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
tar -zxvf DateTimeX-Format-Excel-v1.xx.tar.gz |
|
475
|
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
B<3.> Change (cd) into the extracted directory |
|
477
|
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
B<4.> Run the following commands |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
=over |
|
481
|
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
(For Windows find what version of make was used to compile your perl) |
|
483
|
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
perl -V:make |
|
485
|
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
(then for Windows substitute the correct make function (s/make/dmake/g)?) |
|
487
|
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
=back |
|
489
|
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
>perl Makefile.PL |
|
491
|
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
>make |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
>make test |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
>make install # As sudo/root |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
>make clean |
|
499
|
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
=head1 SUPPORT |
|
501
|
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
=over |
|
503
|
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
L<github DateTimeX::Format::Excel/issues|https://github.com/jandrew/DateTimeX-Format-Excel/issues> |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=back |
|
507
|
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
=head1 TODO |
|
509
|
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
=over |
|
511
|
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
B<1.> Add an error attribute to load soft failures or warnings to |
|
513
|
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
B<2.> Convert Smart::Comments to L<Log::Shiras|https://github.com/jandrew/Log-Shiras> debug lines |
|
515
|
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
B<3.> Allow localization as an input to the data so the object output will localize (DateTime::Local) |
|
517
|
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
=back |
|
519
|
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
=head1 AUTHOR |
|
521
|
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
=over |
|
523
|
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
=item Jed Lund |
|
525
|
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
=item jandrew@cpan.org |
|
527
|
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
=back |
|
529
|
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
=head1 COPYRIGHT |
|
531
|
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
This program is free software; you can redistribute |
|
533
|
|
|
|
|
|
|
it and/or modify it under the same terms as Perl itself. |
|
534
|
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
The full text of the license can be found in the |
|
536
|
|
|
|
|
|
|
LICENSE file included with this module. |
|
537
|
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
This software is copyrighted (c) 2014 - 2016 by Jed Lund |
|
539
|
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
=head1 DEPENDENCIES |
|
541
|
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
=over |
|
543
|
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
B<5.010> - (L<perl>) |
|
545
|
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
L<version> - 0.77 |
|
547
|
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
L<Moose> |
|
549
|
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
L<MooseX::StrictConstructor> |
|
551
|
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
L<MooseX::HasDefaults::RO> |
|
553
|
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
L<DateTime> |
|
555
|
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
L<Carp> |
|
557
|
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
L<Types::Standard> |
|
559
|
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
L<DateTimeX::Format::Excel::Types> |
|
561
|
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
=back |
|
563
|
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
565
|
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
=over |
|
567
|
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
L<DateTime::Format::Excel> |
|
569
|
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
L<Smart::Comments> - Turned on with $ENV{ Smart_Comments } |
|
571
|
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
=back |
|
573
|
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
=cut |
|
575
|
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
#########1#########2 main pod documentation end 5#########6#########7#########8#########9 |