line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package DateTime::Format::Excel;
|
2
|
|
|
|
|
|
|
# $Id: Excel.pm 4458 2010-10-20 09:53:33Z achim66 $
|
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
=head1 NAME
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
DateTime::Format::Excel - Convert between DateTime and Excel dates.
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=cut
|
9
|
|
|
|
|
|
|
|
10
|
3
|
|
|
3
|
|
106178
|
use strict;
|
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
200
|
|
11
|
3
|
|
|
3
|
|
79
|
use 5.005;
|
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
118
|
|
12
|
3
|
|
|
3
|
|
20
|
use Carp;
|
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
771
|
|
13
|
3
|
|
|
3
|
|
5928
|
use DateTime 0.1705;
|
|
3
|
|
|
|
|
786706
|
|
|
3
|
|
|
|
|
121
|
|
14
|
3
|
|
|
3
|
|
34
|
use vars qw( $VERSION );
|
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
2699
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
$VERSION = '0.31';
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
use DateTime::Format::Excel;
|
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
# From Excel via class method:
|
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
my $datetime = DateTime::Format::Excel->parse_datetime( 37680 );
|
25
|
|
|
|
|
|
|
print $datetime->ymd(); # prints 2003-02-28
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
my $datetime = DateTime::Format::Excel->parse_datetime( 40123.625 );
|
28
|
|
|
|
|
|
|
print $datetime->iso8601(); # prints 2009-11-06T15:00:00
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# or via an object
|
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
my $excel = DateTime::Format::Excel->new();
|
33
|
|
|
|
|
|
|
print $excel->parse_datetime( 25569 )->ymd; # prints 1970-01-01
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
# Back to Excel number:
|
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
use DateTime;
|
38
|
|
|
|
|
|
|
my $dt = DateTime->new( year => 1979, month => 7, day => 16 );
|
39
|
|
|
|
|
|
|
my $daynum = DateTime::Format::Excel->format_datetime( $dt );
|
40
|
|
|
|
|
|
|
print $daynum; # prints 29052
|
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
my $dt_with_time = DateTime->new( year => 2010, month => 7, day => 23
|
43
|
|
|
|
|
|
|
, hour => 18, minute => 20 );
|
44
|
|
|
|
|
|
|
my $excel_date = DateTime::Format::Excel->format_datetime( $dt_with_time );
|
45
|
|
|
|
|
|
|
print $excel_date; # prints 40382.763888889
|
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
# or via the object created above
|
48
|
|
|
|
|
|
|
my $other_daynum = $excel->format_datetime( $dt );
|
49
|
|
|
|
|
|
|
print $other_daynum; # prints 29052
|
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
Excel uses a different system for its dates than most Unix programs.
|
54
|
|
|
|
|
|
|
This module allows you to convert between a few of the Excel raw formats
|
55
|
|
|
|
|
|
|
and C objects, which can then be further converted via any
|
56
|
|
|
|
|
|
|
of the other C modules, or just with C's
|
57
|
|
|
|
|
|
|
methods.
|
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
If you happen to be dealing with dates between S<1 Jan 1900> and
|
60
|
|
|
|
|
|
|
S<1 Mar 1900> please read the notes on L.
|
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
Since version 0.30 this modules handles the time part (the decimal
|
63
|
|
|
|
|
|
|
fraction of the Excel time number) correctly, so you can convert
|
64
|
|
|
|
|
|
|
a single point in time to and from Excel format. (Older versions
|
65
|
|
|
|
|
|
|
did only calculate the day number, effectively loosing the time
|
66
|
|
|
|
|
|
|
of day information).
|
67
|
|
|
|
|
|
|
The H:M:S is stored as a fraction where 1 second = 1 / (60*60*24).
|
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
If you're wanting to handle actual spreadsheet files, you may find
|
70
|
|
|
|
|
|
|
L and L of use.
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=head1 CONSTRUCTORS
|
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=head2 new
|
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
Creates a new C instance. This is generally
|
77
|
|
|
|
|
|
|
not required for simple operations. If you wish to use a different
|
78
|
|
|
|
|
|
|
epoch, however, then you'll need to create an object.
|
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
my $excel = DateTime::Format::Excel->new()
|
81
|
|
|
|
|
|
|
my $copy = $excel->new();
|
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
It takes no parameters. If called on an existing object then it
|
84
|
|
|
|
|
|
|
clones the object.
|
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=cut
|
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
sub new
|
89
|
|
|
|
|
|
|
{
|
90
|
12
|
|
|
12
|
1
|
3205
|
my $class = shift;
|
91
|
12
|
100
|
|
|
|
241
|
croak "${class}->new takes no parameters." if @_;
|
92
|
|
|
|
|
|
|
|
93
|
11
|
|
66
|
|
|
65
|
my $self = bless {}, ref($class)||$class;
|
94
|
11
|
100
|
|
|
|
39
|
if (ref $class)
|
95
|
|
|
|
|
|
|
{
|
96
|
|
|
|
|
|
|
# If called on an object, clone
|
97
|
6
|
|
|
|
|
19
|
$self->_epoch( scalar $class->epoch );
|
98
|
|
|
|
|
|
|
# and that's it. we don't store that much info per object
|
99
|
|
|
|
|
|
|
}
|
100
|
|
|
|
|
|
|
|
101
|
11
|
|
|
|
|
33
|
$self;
|
102
|
|
|
|
|
|
|
}
|
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=head2 clone
|
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
This method is provided For those who prefer to explicitly clone via a
|
107
|
|
|
|
|
|
|
method called C. If called as a class method it will die.
|
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
my $clone = $original->clone();
|
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
=cut
|
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
sub clone
|
114
|
|
|
|
|
|
|
{
|
115
|
3
|
|
|
3
|
1
|
530
|
my $self = shift;
|
116
|
3
|
50
|
|
|
|
10
|
croak 'Calling object method as class method!' unless ref $self;
|
117
|
3
|
|
|
|
|
9
|
return $self->new();
|
118
|
|
|
|
|
|
|
}
|
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=head1 CLASS/OBJECT METHODS
|
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
These methods work as both class and object methods.
|
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=head2 parse_datetime
|
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
Given an Excel day number, return a C object representing that
|
127
|
|
|
|
|
|
|
date and time.
|
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# As a class method
|
130
|
|
|
|
|
|
|
my $datetime = DateTime::format::Excel->parse_datetime( 37680 );
|
131
|
|
|
|
|
|
|
print $datetime->ymd('.'); # '2003.02.28'
|
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# Or via an object
|
134
|
|
|
|
|
|
|
my $excel = DateTime::Format::Excel->new();
|
135
|
|
|
|
|
|
|
my $viaobj $excel->parse_datetime( 25569 );
|
136
|
|
|
|
|
|
|
print $viaobj->ymd; # '1970-01-01'
|
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=cut
|
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub parse_datetime
|
141
|
|
|
|
|
|
|
{
|
142
|
35
|
|
|
35
|
1
|
23761
|
my $self = shift;
|
143
|
35
|
50
|
|
|
|
117
|
croak 'No date specified.' unless @_;
|
144
|
35
|
50
|
|
|
|
263
|
croak 'Invalid number of days' unless $_[0] =~ /^ (\d+ (?: (\.\d+ ) )? ) $/x;
|
145
|
35
|
|
|
|
|
88
|
my $excel_days = $1;
|
146
|
35
|
|
|
|
|
54
|
my $excel_secs = $2;
|
147
|
35
|
|
|
|
|
92
|
my $dt = DateTime->new( $self->epoch );
|
148
|
35
|
100
|
|
|
|
10191
|
if(defined $excel_secs){
|
149
|
5
|
|
|
|
|
18
|
$excel_secs = $excel_secs * 86400; # RT7498
|
150
|
5
|
|
|
|
|
14
|
my $excel_nanoseconds = ($excel_secs - int($excel_secs)) * 1_000_000_000;
|
151
|
5
|
|
|
|
|
20
|
$dt->add( days => $excel_days,
|
152
|
|
|
|
|
|
|
seconds => $excel_secs,
|
153
|
|
|
|
|
|
|
nanoseconds => $excel_nanoseconds);
|
154
|
|
|
|
|
|
|
} else {
|
155
|
30
|
|
|
|
|
250
|
$dt->add( days => $excel_days );
|
156
|
|
|
|
|
|
|
}
|
157
|
35
|
|
|
|
|
24578
|
return $dt;
|
158
|
|
|
|
|
|
|
}
|
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
=head2 format_datetime
|
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
Given a C object, return the Excel daynum time.
|
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
use DateTime;
|
165
|
|
|
|
|
|
|
my $dt = DateTime->new( year => 1979, month => 7, day => 16 );
|
166
|
|
|
|
|
|
|
my $daynum = DateTime::Format::Excel->format_datetime( $dt );
|
167
|
|
|
|
|
|
|
print $daynum; # 29052
|
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# or via an object
|
170
|
|
|
|
|
|
|
my $excel = DateTime::Format::Excel->new();
|
171
|
|
|
|
|
|
|
$excel->epoch_mac(); # Let's imagine we want the Mac number
|
172
|
|
|
|
|
|
|
my $mac_daynum = $excel->format_datetime( $dt );
|
173
|
|
|
|
|
|
|
print $mac_daynum; # 27590
|
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=cut
|
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
sub format_datetime
|
179
|
|
|
|
|
|
|
{
|
180
|
35
|
|
|
35
|
1
|
26210
|
my $self = shift;
|
181
|
35
|
50
|
|
|
|
113
|
croak 'No DateTime object specified.' unless @_;
|
182
|
35
|
|
|
|
|
55
|
my $dt = shift;
|
183
|
|
|
|
|
|
|
|
184
|
35
|
|
|
|
|
88
|
my $base = DateTime->new( $self->epoch );
|
185
|
35
|
|
|
|
|
7999
|
my $excel = $dt->jd - $base->jd; # RT7498
|
186
|
|
|
|
|
|
|
|
187
|
35
|
|
|
|
|
808
|
return $excel;
|
188
|
|
|
|
|
|
|
}
|
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=begin _development
|
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=head1 BETA METHODS
|
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
I don't really know whether durations should be handled by this module.
|
195
|
|
|
|
|
|
|
They're nothing interesting.
|
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=cut
|
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
sub parse_duration
|
200
|
|
|
|
|
|
|
{
|
201
|
8
|
|
|
8
|
0
|
3952
|
my $self = shift;
|
202
|
8
|
50
|
|
|
|
22
|
croak 'No duration specified.' unless @_;
|
203
|
8
|
50
|
|
|
|
44
|
croak 'Invalid number of days' unless $_[0] =~ /^ (\d+ (?: \.\d+ )? ) $/x;
|
204
|
8
|
|
|
|
|
21
|
my $days = $1;
|
205
|
|
|
|
|
|
|
|
206
|
8
|
|
|
|
|
33
|
return DateTime::Duration->new( days => $days );
|
207
|
|
|
|
|
|
|
}
|
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
sub format_duration
|
210
|
|
|
|
|
|
|
{
|
211
|
8
|
|
|
8
|
0
|
5583
|
my $self = shift;
|
212
|
8
|
50
|
|
|
|
26
|
croak 'No DateTime::Duration object specified.' unless @_;
|
213
|
|
|
|
|
|
|
|
214
|
8
|
|
|
|
|
28
|
return $_[0]->delta_days();
|
215
|
|
|
|
|
|
|
}
|
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=end _development
|
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=head1 OBJECT METHODS
|
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=head2 epoch
|
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
In scalar context, returns a string identifying the current epoch.
|
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
my $epoch = $excel->epoch();
|
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
Currently either `mac' or `win' with the default being `win'.
|
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
In list context, returns appropriate parameters with which to
|
230
|
|
|
|
|
|
|
create a C object representing the start of the epoch.
|
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
my $base = DateTime->new( $excel->epoch );
|
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=cut
|
235
|
|
|
|
|
|
|
|
236
|
88
|
|
|
88
|
1
|
2992
|
sub epoch { $_[0]->_epoch() }
|
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=head2 epoch_mac
|
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
Set the object to use a Macintosh epoch.
|
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
$excel->epoch_mac(); # epoch is now 1 Jan 1904
|
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
Thus, 1 maps to C<2 Jan 1904>.
|
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=cut
|
247
|
|
|
|
|
|
|
|
248
|
3
|
|
|
3
|
1
|
1266
|
sub epoch_mac { $_[0]->_epoch('mac') }
|
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
=head2 epoch_win
|
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
Set the object to use a Windows Excel epoch.
|
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
$excel->epoch_win(); # epoch is now 30 Dec 1899
|
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
Thus, 2 maps to C<1 Jan 1900>.
|
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
=cut
|
259
|
|
|
|
|
|
|
|
260
|
2
|
|
|
2
|
1
|
1116
|
sub epoch_win { $_[0]->_epoch('win') }
|
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=head1 EPOCHS
|
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
Excel uses ``number of days since S<31 Dec 1899>''. Naturally, Microsoft
|
265
|
|
|
|
|
|
|
messed this up because they happened to believe that 1900 was a leap
|
266
|
|
|
|
|
|
|
year. In this module, we assume what Psion assumed for their Abacus /
|
267
|
|
|
|
|
|
|
Sheet program: S<1 Jan 1900> maps to 2 rather than 1. Thus, 61 maps to
|
268
|
|
|
|
|
|
|
S<1 Mar 1900> in both Excel and this module (and Abacus).
|
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
I has a little option hidden away in its
|
271
|
|
|
|
|
|
|
calculations preferences. It can use either the Windows epoch, or it can
|
272
|
|
|
|
|
|
|
use the Macintosh epoch, which means that the day number is calculated
|
273
|
|
|
|
|
|
|
as ``number of days since S< 1 Jan 1904>''. This module supports both
|
274
|
|
|
|
|
|
|
notations.
|
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
B: the results of this module have only been compared with
|
277
|
|
|
|
|
|
|
I and I on the
|
278
|
|
|
|
|
|
|
I. Where they have differed, I've opted for I's
|
279
|
|
|
|
|
|
|
result rather than I's.
|
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=cut
|
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
{
|
284
|
|
|
|
|
|
|
my %epochs = (
|
285
|
|
|
|
|
|
|
win => [ year => 1899, month => 12, day => 30 ],
|
286
|
|
|
|
|
|
|
mac => [ year => 1904, month => 1, day => 1 ],
|
287
|
|
|
|
|
|
|
);
|
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
sub _epoch
|
290
|
|
|
|
|
|
|
{
|
291
|
99
|
|
|
99
|
|
134
|
my $self = shift;
|
292
|
99
|
100
|
|
|
|
220
|
if (@_)
|
293
|
|
|
|
|
|
|
{
|
294
|
11
|
50
|
|
|
|
31
|
croak 'Calling object method as class method!' unless ref $self;
|
295
|
11
|
50
|
|
|
|
34
|
croak 'Invalid epoch' unless exists $epochs{$_[0]};
|
296
|
11
|
|
|
|
|
48
|
$self->{epoch} = $_[0];
|
297
|
11
|
|
|
|
|
29
|
return $self; # more useful this way, I feel.
|
298
|
|
|
|
|
|
|
}
|
299
|
|
|
|
|
|
|
else
|
300
|
|
|
|
|
|
|
{
|
301
|
88
|
|
|
|
|
105
|
my $epoch;
|
302
|
88
|
100
|
|
|
|
229
|
$epoch = $self->{epoch} if ref $self;
|
303
|
88
|
|
100
|
|
|
302
|
$epoch ||= 'win';
|
304
|
88
|
100
|
|
|
|
217
|
return wantarray ? @{ $epochs{$epoch} } : $epoch;
|
|
70
|
|
|
|
|
475
|
|
305
|
|
|
|
|
|
|
}
|
306
|
|
|
|
|
|
|
}
|
307
|
|
|
|
|
|
|
}
|
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
1;
|
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
__END__
|