← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 18:42:36 2015
Reported on Fri Jul 31 18:48:14 2015

Filename/var/www/foswikidev/core/lib/Foswiki/Time.pm
StatementsExecuted 821 statements in 2.84ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
1111.09ms1.36msFoswiki::Time::::BEGIN@135Foswiki::Time::BEGIN@135
2374543µs595µsFoswiki::Time::::formatTimeFoswiki::Time::formatTime
32276µs149µsFoswiki::Time::::parseTimeFoswiki::Time::parseTime
11114µs28µsFoswiki::Time::::BEGIN@33Foswiki::Time::BEGIN@33
11112µs57µsFoswiki::Time::::BEGIN@57Foswiki::Time::BEGIN@57
1119µs14µsFoswiki::Time::::BEGIN@34Foswiki::Time::BEGIN@34
1119µs34µsFoswiki::Time::::BEGIN@36Foswiki::Time::BEGIN@36
2228µs8µsFoswiki::Time::::importFoswiki::Time::import
1114µs4µsFoswiki::Time::::BEGIN@38Foswiki::Time::BEGIN@38
0000s0sFoswiki::Time::::_daysInYearFoswiki::Time::_daysInYear
0000s0sFoswiki::Time::::_parseDurationFoswiki::Time::_parseDuration
0000s0sFoswiki::Time::::_weekNumberFoswiki::Time::_weekNumber
0000s0sFoswiki::Time::::formatDeltaFoswiki::Time::formatDelta
0000s0sFoswiki::Time::::parseIntervalFoswiki::Time::parseInterval
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# See bottom of file for license and copyright information
2
3=begin TML
4
5---+ package Foswiki::Time
6
7Time handling functions.
8
9*Since* _date_ indicates where functions or parameters have been added since
10the baseline of the API (TWiki release 4.2.3). The _date_ indicates the
11earliest date of a Foswiki release that will support that function or
12parameter.
13
14*Deprecated* _date_ indicates where a function or parameters has been
15[[http://en.wikipedia.org/wiki/Deprecation][deprecated]]. Deprecated
16functions will still work, though they should
17_not_ be called in new plugins and should be replaced in older plugins
18as soon as possible. Deprecated parameters are simply ignored in Foswiki
19releases after _date_.
20
21*Until* _date_ indicates where a function or parameter has been removed.
22The _date_ indicates the latest date at which Foswiki releases still supported
23the function or parameter.
24
25=cut
26
27# THIS PACKAGE IS PART OF THE PUBLISHED API USED BY EXTENSION AUTHORS.
28# DO NOT CHANGE THE EXISTING APIS (well thought out extensions are OK)
29# AND ENSURE ALL POD DOCUMENTATION IS COMPLETE AND ACCURATE.
30
31package Foswiki::Time;
32
33227µs242µs
# spent 28µs (14+14) within Foswiki::Time::BEGIN@33 which was called: # once (14µs+14µs) by Foswiki::BEGIN@644 at line 33
use strict;
# spent 28µs making 1 call to Foswiki::Time::BEGIN@33 # spent 14µs making 1 call to strict::import
34224µs219µs
# spent 14µs (9+5) within Foswiki::Time::BEGIN@34 which was called: # once (9µs+5µs) by Foswiki::BEGIN@644 at line 34
use warnings;
# spent 14µs making 1 call to Foswiki::Time::BEGIN@34 # spent 5µs making 1 call to warnings::import
35
36245µs258µs
# spent 34µs (9+24) within Foswiki::Time::BEGIN@36 which was called: # once (9µs+24µs) by Foswiki::BEGIN@644 at line 36
use Assert;
# spent 34µs making 1 call to Foswiki::Time::BEGIN@36 # spent 24µs making 1 call to Exporter::import
37
38
# spent 4µs within Foswiki::Time::BEGIN@38 which was called: # once (4µs+0s) by Foswiki::BEGIN@644 at line 43
BEGIN {
3915µs if ( $Foswiki::cfg{UseLocale} ) {
40 require locale;
41 import locale();
42 }
43155µs14µs}
# spent 4µs making 1 call to Foswiki::Time::BEGIN@38
44
45# In some environments, e.g. configure, we do NOT want Foswiki.pm
46# use Foswiki::Time qw/-nofoswiki/ for that. Since this module
47# doesn't use Exporter, we don't need anything complicated.
48
49
# spent 8µs within Foswiki::Time::import which was called 2 times, avg 4µs/call: # once (5µs+0s) by Foswiki::Logger::PlainFile::BEGIN@62 at line 62 of /var/www/foswikidev/core/lib/Foswiki/Logger/PlainFile.pm # once (4µs+0s) by Foswiki::Plugins::TablePlugin::Core::BEGIN@10 at line 10 of /var/www/foswikidev/core/lib/Foswiki/Plugins/TablePlugin/Core.pm
sub import {
5021µs my $class = shift;
51
52211µs unless ( @_ && $_[0] eq '-nofoswiki' ) {
53 require Foswiki;
54 }
55}
56
572175µs2103µs
# spent 57µs (12+45) within Foswiki::Time::BEGIN@57 which was called: # once (12µs+45µs) by Foswiki::BEGIN@644 at line 57
use POSIX qw( strftime );
# spent 57µs making 1 call to Foswiki::Time::BEGIN@57 # spent 45µs making 1 call to POSIX::import
58
59# Constants
6013µsour @ISOMONTH = (
61 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
62 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
63);
64
651900nsour @MONTHLENS = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
66
6711µsour @WEEKDAY = ( 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' );
68
6914µsour %MON2NUM = (
70 jan => 0,
71 feb => 1,
72 mar => 2,
73 apr => 3,
74 may => 4,
75 jun => 5,
76 jul => 6,
77 aug => 7,
78 sep => 8,
79 oct => 9,
80 nov => 10,
81 dec => 11
82);
83
84=begin TML
85
86---++ StaticMethod parseTime( $szDate, $defaultLocal ) -> $iSecs
87
88Convert string date/time string to seconds since epoch (1970-01-01T00:00:00Z).
89 * =$sDate= - date/time string
90
91Handles the following formats:
92
93Default Foswiki format
94 * 31 Dec 2001 - 23:59
95 * 31-Dec-2001 - 23:59
96
97Foswiki format without time (defaults to 00:00)
98 * 31 Dec 2001
99 * 31-Dec-2001
100
101Date separated by '/', '.' or '-', time with '.' or ':'
102Date and time separated by ' ', '.' and/or '-'
103 * 2001/12/31 23:59:59
104 * 2001.12.31.23.59.59
105 * 2001/12/31 23:59
106 * 2001.12.31.23.59
107 * 2001-12-31 23:59
108 * 2001-12-31 - 23:59
109 * 2009-1-12
110 * 2009-1
111 * 2009
112
113ISO format
114 * 2001-12-31T23:59:59
115 * 2001-12-31T
116
117ISO dates may have a timezone specifier, either Z or a signed difference
118in hh:mm format. For example:
119 * 2001-12-31T23:59:59+01:00
120 * 2001-12-31T23:59Z
121The default timezone is Z, unless $defaultLocal is true in which case
122the local timezone will be assumed.
123
124If the date format was not recognised, will return undef.
125
126=cut
127
128
# spent 149µs (76+73) within Foswiki::Time::parseTime which was called 3 times, avg 50µs/call: # 2 times (44µs+42µs) by Foswiki::Plugins::TimeCalcPlugin::_TIMESHOWSTORE at line 375 of /var/www/foswikidev/core/lib/Foswiki/Plugins/TimeCalcPlugin.pm, avg 43µs/call # once (32µs+30µs) by Foswiki::Logger::PlainFile::_rotate at line 310 of /var/www/foswikidev/core/lib/Foswiki/Logger/PlainFile.pm
sub parseTime {
12933µs my ( $date, $defaultLocal ) = @_;
130
131 ASSERT( defined $date ) if DEBUG;
13236µs $date =~ s/^\s*//; #remove leading spaces without de-tainting.
133310µs $date =~ s/\s*$//;
134
13521.76ms21.39ms
# spent 1.36ms (1.09+270µs) within Foswiki::Time::BEGIN@135 which was called: # once (1.09ms+270µs) by Foswiki::BEGIN@644 at line 135
use Time::Local qw( timelocal timegm);
# spent 1.36ms making 1 call to Foswiki::Time::BEGIN@135 # spent 26µs making 1 call to Exporter::import
136
137 # NOTE: This routine *will break* if input is not one of below formats!
13835µs my $timelocal =
139 $defaultLocal
140 ? \&Time::Local::timelocal
141 : \&Time::Local::timegm;
142
143 # try "31 Dec 2001 - 23:59" (Foswiki date)
144 # or "31 Dec 2001"
145 #TODO: allow /.: too
146313µs if ( $date =~
147 m/(\d+)[-\s]+([a-z]{3})[-\s]+(\d+)(?:[-\s]+(\d+):(\d+)(?::(\d+))?)?/i )
148 {
14922µs my $year = $3;
150
151 #$year -= 1900 if ( $year > 1900 );
152
15324µs my $mon = $MON2NUM{ lc($2) };
1542600ns return undef unless defined $mon;
155
156 #TODO: %MON2NUM needs to be updated to use i8n
157 #TODO: and should really work for long form of the month name too.
158212µs242µs return &$timelocal( $6 || 0, $5 || 0, $4 || 0, $1, $mon, $year );
# spent 42µs making 2 calls to Time::Local::timegm, avg 21µs/call
159 }
160
161 # ISO date 2001-12-31T23:59:59+01:00
162 # Sven is going to presume that _all_ ISO dated must have a 'T' in them.
16311µs if (
164 ( $date =~ m/T/ )
165 && ( $date =~
166m/(\d\d\d\d)(?:-(\d\d)(?:-(\d\d))?)?(?:T(\d\d)(?::(\d\d)(?::(\d\d(?:\.\d+)?))?)?)?(Z|[-+]\d\d(?::\d\d)?)?/
167 )
168 )
169 {
170 my ( $Y, $M, $D, $h, $m, $s, $tz ) =
171 ( $1, $2 || 1, $3 || 1, $4 || 0, $5 || 0, $6 || 0, $7 || '' );
172 $M--;
173
174 #$Y -= 1900 if ( $Y > 1900 );
175 if ($tz) {
176 my $tzadj = 0;
177 if ( $tz eq 'Z' ) {
178 $tzadj = 0; # Zulu
179 }
180 elsif ( $tz =~ m/([-+])(\d\d)(?::(\d\d))?/ ) {
181 $tzadj = ( $1 || '' ) . ( ( ( $2 * 60 ) + ( $3 || 0 ) ) * 60 );
182 $tzadj -= 0;
183 }
184 return Time::Local::timegm( $s, $m, $h, $D, $M, $Y ) - $tzadj;
185 }
186 return &$timelocal( $s, $m, $h, $D, $M, $Y );
187 }
188
189 #any date that leads with a year (2 digit years too)
19014µs if (
191 $date =~ m|^
192 (\d\d+) #year
193 (?:\s*[/\s.-]\s* #datesep
194 (\d\d?) #month
195 (?:\s*[/\s.-]\s* #datesep
196 (\d\d?) #day
197 (?:\s*[/\s.-]\s* #datetimesep
198 (\d\d?) #hour
199 (?:\s*[:.]\s* #timesep
200 (\d\d?) #min
201 (?:\s*[:.]\s* #timesep
202 (\d\d?)
203 )?
204 )?
205 )?
206 )?
207 )?
208 $|x
209 )
210 {
211
212 #no defaulting yet so we can detect the 2009--12 error
21314µs my ( $year, $M, $D, $h, $m, $s ) = ( $1, $2, $3, $4, $5, $6 );
214
215 # without range checking on the 12 Jan 2009 case above,
216 # there is ambiguity - what is 14 Jan 12 ?
217 # similarly, how would you decide what Jan 02 and 02 Jan are?
218 #$month_p = $MON2NUM{ lc($month_p) } if (defined($MON2NUM{ lc($month_p) }));
219
220 #TODO: unhappily, this means 09 == 1909 not 2009
221 #$year -= 1900 if ( $year > 1900 );
222
223 #range checks
22412µs return undef if ( defined($M) && ( $M < 1 || $M > 12 ) );
2251900ns my $month = ( $M || 1 ) - 1;
22611µs my $monthlength = $MONTHLENS[$month];
227
228 # If leap year, note February is month number 1 starting from 0
2291400ns $monthlength = 29 if ( $month == 1 && _daysInYear($year) == 366 );
2301700ns return undef if ( defined($D) && ( $D < 0 || $D > $monthlength ) );
2311300ns return undef if ( defined($h) && ( $h < 0 || $h > 24 ) );
2321300ns return undef if ( defined($m) && ( $m < 0 || $m > 60 ) );
2331300ns return undef if ( defined($s) && ( $s < 0 || $s > 60 ) );
234
235 #return undef if ( defined($year) && $year < 60 );
236
2371600ns my $day = $D || 1;
2381400ns my $hour = $h || 0;
2391300ns my $min = $m || 0;
2401200ns my $sec = $s || 0;
241
24218µs130µs return &$timelocal( $sec, $min, $hour, $day, $month, $year );
# spent 30µs making 1 call to Time::Local::timegm
243 }
244
245 # give up, return undef
246 return undef;
247}
248
249=begin TML
250
251---++ StaticMethod formatTime ($epochSeconds, $formatString, $outputTimeZone) -> $value
252
253 * =$epochSeconds= epochSecs GMT
254 * =$formatString= Foswiki time date format, default =$day $month $year - $hour:$min=
255 * =$outputTimeZone= timezone to display, =gmtime= or =servertime=, default is whatever is set in $Foswiki::cfg{DisplayTimeValues}
256
257=$formatString= supports:
258 | $seconds | secs |
259 | $minutes | mins |
260 | $hours | hours |
261 | $day | day |
262 | $wday | weekday name |
263 | $dow | day number (0 = Sunday) |
264 | $week | week number |
265 | $we | week number (~ISO 8601) |
266 | $month | month name |
267 | $mo | month number |
268 | $year | 4-digit year |
269 | $ye | 2-digit year |
270 | $http | ful HTTP header format date/time |
271 | $email | full email format date/time |
272 | $rcs | full RCS format date/time |
273 | $epoch | seconds since 1st January 1970 |
274 | $tz | Timezone name (GMT or Local) |
275 | $isotz | ISO 8601 timezone specifier e.g. 'Z, '+07:15' |
276
277=cut
278
279# previous known as Foswiki::formatTime
280
281
# spent 595µs (543+53) within Foswiki::Time::formatTime which was called 23 times, avg 26µs/call: # 8 times (69µs+0s) by Foswiki::Render::renderRevisionInfo at line 866 of /var/www/foswikidev/core/lib/Foswiki/Render.pm, avg 9µs/call # 6 times (141µs+0s) by Foswiki::__ANON__[/var/www/foswikidev/core/lib/Foswiki.pm:247] at line 245 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 24µs/call # 3 times (65µs+0s) by Foswiki::Render::renderRevisionInfo at line 862 of /var/www/foswikidev/core/lib/Foswiki/Render.pm, avg 22µs/call # 2 times (103µs+24µs) by Foswiki::__ANON__[/var/www/foswikidev/core/lib/Foswiki.pm:323] at line 321 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 64µs/call # 2 times (47µs+0s) by Foswiki::Plugins::TimeCalcPlugin::_TIMESHOWSTORE at line 398 of /var/www/foswikidev/core/lib/Foswiki/Plugins/TimeCalcPlugin.pm, avg 23µs/call # once (57µs+28µs) by Foswiki::Logger::PlainFile::log at line 121 of /var/www/foswikidev/core/lib/Foswiki/Logger/PlainFile.pm # once (61µs+0s) by Foswiki::Render::renderRevisionInfo at line 860 of /var/www/foswikidev/core/lib/Foswiki/Render.pm
sub formatTime {
2822346µs my ( $epochSeconds, $formatString, $outputTimeZone ) = @_;
283237µs my $value = $epochSeconds;
284
285 ASSERT( defined $epochSeconds ) if DEBUG;
286
287 # use default Foswiki format "31 Dec 1999 - 23:59" unless specified
288234µs $formatString ||= '$longdate';
289237µs $outputTimeZone ||= $Foswiki::cfg{DisplayTimeValues};
290
2912319µs if ( $formatString =~ m/http/i ) {
292 $outputTimeZone = 'gmtime';
293 }
294
295238µs my ( $sec, $min, $hour, $day, $mon, $year, $wday, $yday, $isdst );
296234µs my ( $tz_str, $isotz_str );
2972312µs if ( $outputTimeZone eq 'servertime' ) {
298339µs ( $sec, $min, $hour, $day, $mon, $year, $wday, $yday, $isdst ) =
299 localtime($epochSeconds);
300
301 # SMELL: how do we get the different timezone strings (and when
302 # we add usertime, then what?)
30331µs $tz_str = 'Local';
304
305 # isotz_str is date dependant, ie different in summer and winter time
306368µs352µs $isotz_str = strftime(
# spent 52µs making 3 calls to POSIX::strftime, avg 18µs/call
307 '%z', $sec, $min, $hour, $day,
308 $mon, $year, $wday, $yday, $isdst
309 );
310317µs $isotz_str =~ s/([+-]\d\d)(\d\d)/$1:$2/;
311 }
312 else {
3132045µs ( $sec, $min, $hour, $day, $mon, $year, $wday, $yday ) =
314 gmtime($epochSeconds);
315206µs $tz_str = 'GMT';
316205µs $isotz_str = 'Z';
317 }
318
319 #standard Foswiki date time formats
320
321 # RCS format, example: "2001/12/31 23:59:59"
3222312µs $formatString =~ s/\$rcs/\$year\/\$mo\/\$day \$hour:\$min:\$sec/gi;
323
324 # HTTP and email header format, e.g. "Thu, 23 Jul 1998 07:21:56 EST"
325 # RFC 822/2616/1123
3262321µs $formatString =~
327 s/\$(http|email)/\$wday, \$day \$month \$year \$hour:\$min:\$sec \$tz/gi;
328
329 # ISO Format, see spec at http://www.w3.org/TR/NOTE-datetime
330 # e.g. "2002-12-31T19:30:12Z"
331 # Undocumented: formatString='iso'
3322313µs $formatString = '$year-$mo-$dayT$hour:$min:$sec$isotz'
333 if lc($formatString) eq 'iso';
334
335 # Undocumented, but used in renderers: formatString can contain '$iso'
336238µs $formatString =~ s/\$iso\b/\$year-\$mo-\$dayT\$hour:\$min:\$sec\$isotz/gi;
337
338 # longdate
3392321µs $formatString =~
340 s/\$longdate/$Foswiki::cfg{DefaultDateFormat} - \$hour:\$min/gi;
341
342235µs $value = $formatString;
3432315µs $value =~ s/\$seco?n?d?s?/sprintf('%.2u',$sec)/gei;
3442317µs $value =~ s/\$minu?t?e?s?/sprintf('%.2u',$min)/gei;
3452314µs $value =~ s/\$hour?s?/sprintf('%.2u',$hour)/gei;
3462321µs $value =~ s/\$day/sprintf('%.2u',$day)/gei;
3472311µs $value =~ s/\$wday/$WEEKDAY[$wday]/gi;
348237µs $value =~ s/\$dow/$wday/gi;
349236µs $value =~ s/\$week/_weekNumber($wday, $yday, $year + 1900)/egi;
350236µs $value =~ s/\$we/substr('0'._weekNumber($wday, $yday, $year + 1900),-2)/egi;
3512317µs $value =~ s/\$mont?h?/$ISOMONTH[$mon]/gi;
352239µs $value =~ s/\$mo/sprintf('%.2u',$mon+1)/gei;
3532322µs $value =~ s/\$year?/sprintf('%.4u',$year + 1900)/gei;
354236µs $value =~ s/\$ye/sprintf('%.2u',$year%100)/gei;
3552321µs $value =~ s/\$epoch/$epochSeconds/gi;
356236µs $value =~ s/\$tz/$tz_str/gi;
357236µs $value =~ s/\$isotz/$isotz_str/gi;
358
3592387µs return $value;
360}
361
362# Returns the ISO8601 week number for a date.
363# Year is the real year
364# Day of week is 0..6 where 0==Sunday
365# Day of year is 0..364 (or 365) where 0==Jan1
366# From http://www.perlmonks.org/?node_id=710571
367sub _weekNumber {
368 my ( $dayOfWeek, $dayOfYear, $year ) = @_;
369
370 # rebase dow to Monday==0
371 $dayOfWeek = ( $dayOfWeek + 6 ) % 7;
372
373 # Locate the nearest Thursday, by locating the Monday at
374 # or before and going forwards 3 days)
375 my $dayOfNearestThurs = $dayOfYear - $dayOfWeek + 3;
376
377 my $daysInThisYear = _daysInYear($year);
378
379#print STDERR "dow:$dayOfWeek, doy:$dayOfYear, $year = thu:$dayOfNearestThurs ($daysInThisYear)\n";
380
381 # Is nearest thursday in last year or next year?
382 if ( $dayOfNearestThurs < 0 ) {
383
384 # Nearest Thurs is last year
385 # We are at the start of the year
386 # Adjust by the number of days in LAST year
387 $dayOfNearestThurs += _daysInYear( $year - 1 );
388 }
389 if ( $dayOfNearestThurs >= $daysInThisYear ) {
390
391 # Nearest Thurs is next year
392 # We are at the end of the year
393 # Adjust by the number of days in THIS year
394 $dayOfNearestThurs -= $daysInThisYear;
395 }
396
397 # Which week does the Thurs fall into?
398 return int( $dayOfNearestThurs / 7 ) + 1;
399}
400
401# Returns the number of...
402sub _daysInYear {
403 return 366 unless $_[0] % 400;
404 return 365 unless $_[0] % 100;
405 return 366 unless $_[0] % 4;
406 return 365;
407}
408
409=begin TML
410
411---++ StaticMethod formatDelta( $s ) -> $string
412
413Format a time in seconds as a string. For example,
414"1 day, 3 hours, 2 minutes, 6 seconds"
415
416=cut
417
418sub formatDelta {
419 my $secs = shift;
420 my $language = shift;
421
422 ASSERT( defined $secs ) if DEBUG;
423 my $rem = $secs % ( 60 * 60 * 24 );
424 my $days = ( $secs - $rem ) / ( 60 * 60 * 24 );
425 $secs = $rem;
426
427 $rem = $secs % ( 60 * 60 );
428 my $hours = ( $secs - $rem ) / ( 60 * 60 );
429 $secs = $rem;
430
431 $rem = $secs % 60;
432 my $mins = ( $secs - $rem ) / 60;
433 $secs = $rem;
434
435 my $str = '';
436
437 if ($language) {
438
439 #format as in user's language
440 if ($days) {
441 $str .= $language->maketext( '[*,_1,day] ', $days );
442 }
443 if ($hours) {
444 $str .= $language->maketext( '[*,_1,hour] ', $hours );
445 }
446 if ($mins) {
447 $str .= $language->maketext( '[*,_1,minute] ', $mins );
448 }
449 if ($secs) {
450 $str .= $language->maketext( '[*,_1,second] ', $secs );
451 }
452 }
453 else {
454
455 #original code, harcoded English (BAD)
456 if ($days) {
457 $str .= $days . ' day' . ( $days > 1 ? 's ' : ' ' );
458 }
459 if ($hours) {
460 $str .= $hours . ' hour' . ( $hours > 1 ? 's ' : ' ' );
461 }
462 if ($mins) {
463 $str .= $mins . ' minute' . ( $mins > 1 ? 's ' : ' ' );
464 }
465 if ($secs) {
466 $str .= $secs . ' second' . ( $secs > 1 ? 's ' : ' ' );
467 }
468 }
469 $str =~ s/\s+$//;
470 return $str;
471}
472
473=begin TML
474
475---++ StaticMethod parseInterval( $szInterval ) -> [$iSecs, $iSecs]
476
477Convert string representing a time interval to a pair of integers
478representing the amount of seconds since epoch for the start and end
479extremes of the time interval.
480
481 * =$szInterval= - time interval string
482
483in yacc syntax, grammar and actions:
484<verbatim>
485interval ::= date { $$.start = fillStart($1); $$.end = fillEnd($1); }
486 | date '/' date { $$.start = fillStart($1); $$.end = fillEnd($3); }
487 | 'P' duration '/' date { $$.start = fillEnd($4)-$2; $$.end = fillEnd($4); }
488 | date '/' 'P' duration { $$.start = fillStart($1); $$.end = fillStart($1)+$4; }
489 ;
490</verbatim>
491an =interval= may be followed by a timezone specification string (this is not supported yet).
492
493=duration= has the form (regular expression):
494<verbatim>
495 P(<number><nameOfDuration>)+
496</verbatim>
497
498nameOfDuration may be one of:
499 * y(year), m(month), w(week), d(day), h(hour), M(minute), S(second)
500
501=date= follows ISO8601 and must include hyphens. (any amount of trailing
502 elements may be omitted and will be filled in differently on the
503 differents ends of the interval as to include the longest possible
504 interval):
505
506 * 2001-01-01T00:00:00
507 * 2001-12-31T23:59:59
508
509timezone is optional. Default is local time.
510
511If the format is not recognised, will return empty interval [0,0].
512
513=cut
514
515# TODO: timezone testing, especially on non valid strings
516
517sub parseInterval {
518 my ($interval) = @_;
519 my @lt = localtime();
520 my $today = sprintf( '%04d-%02d-%02d', $lt[5] + 1900, $lt[4] + 1, $lt[3] );
521 my $now = $today . sprintf( 'T%02d:%02d:%02d', $lt[2], $lt[1], $lt[0] );
522
523 ASSERT( defined $interval ) if DEBUG;
524
525 # replace $now and $today shortcuts
526 $interval =~ s/\$today/$today/g;
527 $interval =~ s/\$now/$now/g;
528
529 # if $theDate does not contain a '/': force it to do so.
530 $interval = $interval . '/' . $interval
531 unless ( $interval =~ m/\// );
532
533 my ( $first, $last ) = split( /\//, $interval, 2 );
534 my ( $start, $end );
535
536 # first translate dates into seconds from epoch,
537 # in the second loop we will examine interval durations.
538
539 if ( $first !~ /^P/ ) {
540
541 # complete with parts from "-01-01T00:00:00"
542 if ( length($first) < length('0000-01-01T00:00:00') ) {
543 $first .= substr( '0000-01-01T00:00:00', length($first) );
544 }
545 $start = parseTime( $first, 1 );
546 }
547
548 if ( $last !~ /^P/ ) {
549
550 # complete with parts from "-12-31T23:59:60"
551 # check last day of month
552 if ( length($last) == 7 ) {
553 my $month = substr( $last, 5 );
554 my $year = substr( $last, 0, 4 );
555 my $monthlength = $MONTHLENS[ $month - 1 ];
556
557 # If leap year, note February is month number 2 here
558 $monthlength = 29 if ( $month == 2 && _daysInYear($year) == 366 );
559 $last .= '-' . $monthlength;
560 }
561 if ( length($last) < length('0000-12-31T23:59:59') ) {
562 $last .= substr( '0000-12-31T23:59:59', length($last) );
563 }
564 $end = parseTime( $last, 1 );
565 }
566
567 if ( !defined($start) ) {
568 $start = ( $end || 0 ) - _parseDuration($first);
569 }
570 if ( !defined($end) ) {
571 $end = $start + _parseDuration($last);
572 }
573 return ( $start || 0, $end || 0 );
574}
575
576sub _parseDuration {
577 my $s = shift;
578 my $d = 0;
579 $s =~ s/(\d+)y/$d += $1 * 31556925;''/gei; # tropical year
580 $s =~ s/(\d+)m/$d += $1 * 2592000; ''/ge; # 1m = 30 days
581 $s =~ s/(\d+)w/$d += $1 * 604800; ''/gei; # 1w = 7 days
582 $s =~ s/(\d+)d/$d += $1 * 86400; ''/gei; # 1d = 24 hours
583 $s =~ s/(\d+)h/$d += $1 * 3600; ''/gei; # 1 hour = 60 mins
584 $s =~ s/(\d+)M/$d += $1 * 60; ''/ge; # note: m != M
585 $s =~ s/(\d+)S/$d += $1 * 1; ''/gei;
586 return $d;
587}
588
58917µs1;
590__END__