Filename | /var/www/foswikidev/core/lib/Foswiki/Time.pm |
Statements | Executed 821 statements in 2.84ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 1.09ms | 1.36ms | BEGIN@135 | Foswiki::Time::
23 | 7 | 4 | 543µs | 595µs | formatTime | Foswiki::Time::
3 | 2 | 2 | 76µs | 149µs | parseTime | Foswiki::Time::
1 | 1 | 1 | 14µs | 28µs | BEGIN@33 | Foswiki::Time::
1 | 1 | 1 | 12µs | 57µs | BEGIN@57 | Foswiki::Time::
1 | 1 | 1 | 9µs | 14µs | BEGIN@34 | Foswiki::Time::
1 | 1 | 1 | 9µs | 34µs | BEGIN@36 | Foswiki::Time::
2 | 2 | 2 | 8µs | 8µs | import | Foswiki::Time::
1 | 1 | 1 | 4µs | 4µs | BEGIN@38 | Foswiki::Time::
0 | 0 | 0 | 0s | 0s | _daysInYear | Foswiki::Time::
0 | 0 | 0 | 0s | 0s | _parseDuration | Foswiki::Time::
0 | 0 | 0 | 0s | 0s | _weekNumber | Foswiki::Time::
0 | 0 | 0 | 0s | 0s | formatDelta | Foswiki::Time::
0 | 0 | 0 | 0s | 0s | parseInterval | Foswiki::Time::
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 | |||||
7 | Time handling functions. | ||||
8 | |||||
9 | *Since* _date_ indicates where functions or parameters have been added since | ||||
10 | the baseline of the API (TWiki release 4.2.3). The _date_ indicates the | ||||
11 | earliest date of a Foswiki release that will support that function or | ||||
12 | parameter. | ||||
13 | |||||
14 | *Deprecated* _date_ indicates where a function or parameters has been | ||||
15 | [[http://en.wikipedia.org/wiki/Deprecation][deprecated]]. Deprecated | ||||
16 | functions will still work, though they should | ||||
17 | _not_ be called in new plugins and should be replaced in older plugins | ||||
18 | as soon as possible. Deprecated parameters are simply ignored in Foswiki | ||||
19 | releases after _date_. | ||||
20 | |||||
21 | *Until* _date_ indicates where a function or parameter has been removed. | ||||
22 | The _date_ indicates the latest date at which Foswiki releases still supported | ||||
23 | the 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 | |||||
31 | package Foswiki::Time; | ||||
32 | |||||
33 | 2 | 27µs | 2 | 42µ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 # spent 28µs making 1 call to Foswiki::Time::BEGIN@33
# spent 14µs making 1 call to strict::import |
34 | 2 | 24µs | 2 | 19µ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 # spent 14µs making 1 call to Foswiki::Time::BEGIN@34
# spent 5µs making 1 call to warnings::import |
35 | |||||
36 | 2 | 45µs | 2 | 58µ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 # 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 | ||||
39 | 1 | 5µs | if ( $Foswiki::cfg{UseLocale} ) { | ||
40 | require locale; | ||||
41 | import locale(); | ||||
42 | } | ||||
43 | 1 | 55µs | 1 | 4µ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 | ||||
50 | 2 | 1µs | my $class = shift; | ||
51 | |||||
52 | 2 | 11µs | unless ( @_ && $_[0] eq '-nofoswiki' ) { | ||
53 | require Foswiki; | ||||
54 | } | ||||
55 | } | ||||
56 | |||||
57 | 2 | 175µs | 2 | 103µ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 # spent 57µs making 1 call to Foswiki::Time::BEGIN@57
# spent 45µs making 1 call to POSIX::import |
58 | |||||
59 | # Constants | ||||
60 | 1 | 3µs | our @ISOMONTH = ( | ||
61 | 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', | ||||
62 | 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' | ||||
63 | ); | ||||
64 | |||||
65 | 1 | 900ns | our @MONTHLENS = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ); | ||
66 | |||||
67 | 1 | 1µs | our @WEEKDAY = ( 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ); | ||
68 | |||||
69 | 1 | 4µs | our %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 | |||||
88 | Convert string date/time string to seconds since epoch (1970-01-01T00:00:00Z). | ||||
89 | * =$sDate= - date/time string | ||||
90 | |||||
91 | Handles the following formats: | ||||
92 | |||||
93 | Default Foswiki format | ||||
94 | * 31 Dec 2001 - 23:59 | ||||
95 | * 31-Dec-2001 - 23:59 | ||||
96 | |||||
97 | Foswiki format without time (defaults to 00:00) | ||||
98 | * 31 Dec 2001 | ||||
99 | * 31-Dec-2001 | ||||
100 | |||||
101 | Date separated by '/', '.' or '-', time with '.' or ':' | ||||
102 | Date 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 | |||||
113 | ISO format | ||||
114 | * 2001-12-31T23:59:59 | ||||
115 | * 2001-12-31T | ||||
116 | |||||
117 | ISO dates may have a timezone specifier, either Z or a signed difference | ||||
118 | in hh:mm format. For example: | ||||
119 | * 2001-12-31T23:59:59+01:00 | ||||
120 | * 2001-12-31T23:59Z | ||||
121 | The default timezone is Z, unless $defaultLocal is true in which case | ||||
122 | the local timezone will be assumed. | ||||
123 | |||||
124 | If 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 | ||||
129 | 3 | 3µs | my ( $date, $defaultLocal ) = @_; | ||
130 | |||||
131 | ASSERT( defined $date ) if DEBUG; | ||||
132 | 3 | 6µs | $date =~ s/^\s*//; #remove leading spaces without de-tainting. | ||
133 | 3 | 10µs | $date =~ s/\s*$//; | ||
134 | |||||
135 | 2 | 1.76ms | 2 | 1.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 # 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! | ||||
138 | 3 | 5µ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 | ||||
146 | 3 | 13µs | if ( $date =~ | ||
147 | m/(\d+)[-\s]+([a-z]{3})[-\s]+(\d+)(?:[-\s]+(\d+):(\d+)(?::(\d+))?)?/i ) | ||||
148 | { | ||||
149 | 2 | 2µs | my $year = $3; | ||
150 | |||||
151 | #$year -= 1900 if ( $year > 1900 ); | ||||
152 | |||||
153 | 2 | 4µs | my $mon = $MON2NUM{ lc($2) }; | ||
154 | 2 | 600ns | 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. | ||||
158 | 2 | 12µs | 2 | 42µ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. | ||||
163 | 1 | 1µs | if ( | ||
164 | ( $date =~ m/T/ ) | ||||
165 | && ( $date =~ | ||||
166 | m/(\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) | ||||
190 | 1 | 4µ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 | ||||
213 | 1 | 4µ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 | ||||
224 | 1 | 2µs | return undef if ( defined($M) && ( $M < 1 || $M > 12 ) ); | ||
225 | 1 | 900ns | my $month = ( $M || 1 ) - 1; | ||
226 | 1 | 1µs | my $monthlength = $MONTHLENS[$month]; | ||
227 | |||||
228 | # If leap year, note February is month number 1 starting from 0 | ||||
229 | 1 | 400ns | $monthlength = 29 if ( $month == 1 && _daysInYear($year) == 366 ); | ||
230 | 1 | 700ns | return undef if ( defined($D) && ( $D < 0 || $D > $monthlength ) ); | ||
231 | 1 | 300ns | return undef if ( defined($h) && ( $h < 0 || $h > 24 ) ); | ||
232 | 1 | 300ns | return undef if ( defined($m) && ( $m < 0 || $m > 60 ) ); | ||
233 | 1 | 300ns | return undef if ( defined($s) && ( $s < 0 || $s > 60 ) ); | ||
234 | |||||
235 | #return undef if ( defined($year) && $year < 60 ); | ||||
236 | |||||
237 | 1 | 600ns | my $day = $D || 1; | ||
238 | 1 | 400ns | my $hour = $h || 0; | ||
239 | 1 | 300ns | my $min = $m || 0; | ||
240 | 1 | 200ns | my $sec = $s || 0; | ||
241 | |||||
242 | 1 | 8µs | 1 | 30µ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 | ||||
282 | 23 | 46µs | my ( $epochSeconds, $formatString, $outputTimeZone ) = @_; | ||
283 | 23 | 7µs | my $value = $epochSeconds; | ||
284 | |||||
285 | ASSERT( defined $epochSeconds ) if DEBUG; | ||||
286 | |||||
287 | # use default Foswiki format "31 Dec 1999 - 23:59" unless specified | ||||
288 | 23 | 4µs | $formatString ||= '$longdate'; | ||
289 | 23 | 7µs | $outputTimeZone ||= $Foswiki::cfg{DisplayTimeValues}; | ||
290 | |||||
291 | 23 | 19µs | if ( $formatString =~ m/http/i ) { | ||
292 | $outputTimeZone = 'gmtime'; | ||||
293 | } | ||||
294 | |||||
295 | 23 | 8µs | my ( $sec, $min, $hour, $day, $mon, $year, $wday, $yday, $isdst ); | ||
296 | 23 | 4µs | my ( $tz_str, $isotz_str ); | ||
297 | 23 | 12µs | if ( $outputTimeZone eq 'servertime' ) { | ||
298 | 3 | 39µ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?) | ||||
303 | 3 | 1µs | $tz_str = 'Local'; | ||
304 | |||||
305 | # isotz_str is date dependant, ie different in summer and winter time | ||||
306 | 3 | 68µs | 3 | 52µ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 | ); | ||||
310 | 3 | 17µs | $isotz_str =~ s/([+-]\d\d)(\d\d)/$1:$2/; | ||
311 | } | ||||
312 | else { | ||||
313 | 20 | 45µs | ( $sec, $min, $hour, $day, $mon, $year, $wday, $yday ) = | ||
314 | gmtime($epochSeconds); | ||||
315 | 20 | 6µs | $tz_str = 'GMT'; | ||
316 | 20 | 5µs | $isotz_str = 'Z'; | ||
317 | } | ||||
318 | |||||
319 | #standard Foswiki date time formats | ||||
320 | |||||
321 | # RCS format, example: "2001/12/31 23:59:59" | ||||
322 | 23 | 12µ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 | ||||
326 | 23 | 21µ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' | ||||
332 | 23 | 13µ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' | ||||
336 | 23 | 8µs | $formatString =~ s/\$iso\b/\$year-\$mo-\$dayT\$hour:\$min:\$sec\$isotz/gi; | ||
337 | |||||
338 | # longdate | ||||
339 | 23 | 21µs | $formatString =~ | ||
340 | s/\$longdate/$Foswiki::cfg{DefaultDateFormat} - \$hour:\$min/gi; | ||||
341 | |||||
342 | 23 | 5µs | $value = $formatString; | ||
343 | 23 | 15µs | $value =~ s/\$seco?n?d?s?/sprintf('%.2u',$sec)/gei; | ||
344 | 23 | 17µs | $value =~ s/\$minu?t?e?s?/sprintf('%.2u',$min)/gei; | ||
345 | 23 | 14µs | $value =~ s/\$hour?s?/sprintf('%.2u',$hour)/gei; | ||
346 | 23 | 21µs | $value =~ s/\$day/sprintf('%.2u',$day)/gei; | ||
347 | 23 | 11µs | $value =~ s/\$wday/$WEEKDAY[$wday]/gi; | ||
348 | 23 | 7µs | $value =~ s/\$dow/$wday/gi; | ||
349 | 23 | 6µs | $value =~ s/\$week/_weekNumber($wday, $yday, $year + 1900)/egi; | ||
350 | 23 | 6µs | $value =~ s/\$we/substr('0'._weekNumber($wday, $yday, $year + 1900),-2)/egi; | ||
351 | 23 | 17µs | $value =~ s/\$mont?h?/$ISOMONTH[$mon]/gi; | ||
352 | 23 | 9µs | $value =~ s/\$mo/sprintf('%.2u',$mon+1)/gei; | ||
353 | 23 | 22µs | $value =~ s/\$year?/sprintf('%.4u',$year + 1900)/gei; | ||
354 | 23 | 6µs | $value =~ s/\$ye/sprintf('%.2u',$year%100)/gei; | ||
355 | 23 | 21µs | $value =~ s/\$epoch/$epochSeconds/gi; | ||
356 | 23 | 6µs | $value =~ s/\$tz/$tz_str/gi; | ||
357 | 23 | 6µs | $value =~ s/\$isotz/$isotz_str/gi; | ||
358 | |||||
359 | 23 | 87µ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 | ||||
367 | sub _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... | ||||
402 | sub _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 | |||||
413 | Format a time in seconds as a string. For example, | ||||
414 | "1 day, 3 hours, 2 minutes, 6 seconds" | ||||
415 | |||||
416 | =cut | ||||
417 | |||||
418 | sub 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 | |||||
477 | Convert string representing a time interval to a pair of integers | ||||
478 | representing the amount of seconds since epoch for the start and end | ||||
479 | extremes of the time interval. | ||||
480 | |||||
481 | * =$szInterval= - time interval string | ||||
482 | |||||
483 | in yacc syntax, grammar and actions: | ||||
484 | <verbatim> | ||||
485 | interval ::= 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> | ||||
491 | an =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 | |||||
498 | nameOfDuration 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 | |||||
509 | timezone is optional. Default is local time. | ||||
510 | |||||
511 | If 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 | |||||
517 | sub 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 | |||||
576 | sub _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 | |||||
589 | 1 | 7µs | 1; | ||
590 | __END__ |