File Coverage

lib/Sisimai/DateTime.pm
Criterion Covered Total %
statement 140 146 95.8
branch 78 90 86.6
condition 50 74 67.5
subroutine 14 14 100.0
pod 3 7 42.8
total 285 331 86.1


line stmt bran cond sub pod time code
1             package Sisimai::DateTime;
2 79     79   71419 use feature ':5.10';
  79         217  
  79         5770  
3 79     79   487 use strict;
  79         198  
  79         2123  
4 79     79   468 use warnings;
  79         242  
  79         2350  
5 79     79   1043 use Time::Piece;
  79         13443  
  79         528  
6              
7             sub BASE_D() { 86400 } # 1 day = 86400 sec
8             sub BASE_Y() { 365.2425 } # 1 year = 365.2425 days
9             sub BASE_L() { 29.53059 } # 1 lunar month = 29.53059 days
10             sub CONST_P() { 4 * atan2(1,1) } # PI, 3.1415926535
11             sub CONST_E() { exp(1) } # e, Napier's constant
12             sub TZ_OFFSET() { 54000 } # Max time zone offset, 54000 seconds
13              
14 79         7896 use constant MonthName => {
15             'full' => [qw|January February March April May June July August September October November December|],
16             'abbr' => [qw|Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec|],
17 79     79   22146 };
  79         240  
18 79         14292 use constant DayOfWeek => {
19             'full' => [qw|Sunday Monday Tuesday Wednesday Thursday Friday Saturday|],
20             'abbr' => [qw|Sun Mon Tue Wed Thu Fri Sat|],
21 79     79   556 };
  79         229  
22 79         198577 use constant TimeZones => {
23             # http://en.wikipedia.org/wiki/List_of_time_zone_abbreviations
24             #'ACDT' => '+1030', # Australian Central Daylight Time UTC+10:30
25             #'ACST' => '+0930', # Australian Central Standard Time UTC+09:30
26             #'ACT' => '+0800', # ASEAN Common Time UTC+08:00
27             'ADT' => '-0300', # Atlantic Daylight Time UTC-03:00
28             #'AEDT' => '+1100', # Australian Eastern Daylight Time UTC+11:00
29             #'AEST' => '+1000', # Australian Eastern Standard Time UTC+10:00
30             #'AFT' => '+0430', # Afghanistan Time UTC+04:30
31             'AKDT' => '-0800', # Alaska Daylight Time UTC-08:00
32             'AKST' => '-0900', # Alaska Standard Time UTC-09:00
33             #'AMST' => '+0500', # Armenia Summer Time UTC+05:00
34             #'AMT' => '+0400', # Armenia Time UTC+04:00
35             #'ART' => '-0300', # Argentina Time UTC+03:00
36             #'AST' => '+0300', # Arab Standard Time (Kuwait, Riyadh) UTC+03:00
37             #'AST' => '+0400', # Arabian Standard Time (Abu Dhabi, Muscat) UTC+04:00
38             #'AST' => '+0300', # Arabic Standard Time (Baghdad) UTC+03:00
39             'AST' => '-0400', # Atlantic Standard Time UTC-04:00
40             #'AWDT' => '+0900', # Australian Western Daylight Time UTC+09:00
41             #'AWST' => '+0800', # Australian Western Standard Time UTC+08:00
42             #'AZOST'=> '-0100', # Azores Standard Time UTC-01:00
43             #'AZT' => '+0400', # Azerbaijan Time UTC+04:00
44             #'BDT' => '+0800', # Brunei Time UTC+08:00
45             #'BIOT' => '+0600', # British Indian Ocean Time UTC+06:00
46             #'BIT' => '-1200', # Baker Island Time UTC-12:00
47             #'BOT' => '-0400', # Bolivia Time UTC-04:00
48             #'BRT' => '-0300', # Brasilia Time UTC-03:00
49             #'BST' => '+0600', # Bangladesh Standard Time UTC+06:00
50             #'BST' => '+0100', # British Summer Time (British Standard Time from Feb 1968 to Oct 1971) UTC+01:00
51             #'BTT' => '+0600', # Bhutan Time UTC+06:00
52             #'CAT' => '+0200', # Central Africa Time UTC+02:00
53             #'CCT' => '+0630', # Cocos Islands Time UTC+06:30
54             'CDT' => '-0500', # Central Daylight Time (North America) UTC-05:00
55             #'CEDT' => '+0200', # Central European Daylight Time UTC+02:00
56             #'CEST' => '+0200', # Central European Summer Time UTC+02:00
57             #'CET' => '+0100', # Central European Time UTC+01:00
58             #'CHAST'=> '+1245', # Chatham Standard Time UTC+12:45
59             #'CIST' => '-0800', # Clipperton Island Standard Time UTC-08:00
60             #'CKT' => '-1000', # Cook Island Time UTC-10:00
61             #'CLST' => '-0300', # Chile Summer Time UTC-03:00
62             #'CLT' => '-0400', # Chile Standard Time UTC-04:00
63             #'COST' => '-0400', # Colombia Summer Time UTC-04:00
64             #'COT' => '-0500', # Colombia Time UTC-05:00
65             'CST' => '-0600', # Central Standard Time (North America) UTC-06:00
66             #'CST' => '+0800', # China Standard Time UTC+08:00
67             #'CVT' => '-0100', # Cape Verde Time UTC-01:00
68             #'CXT' => '+0700', # Christmas Island Time UTC+07:00
69             #'ChST' => '+1000', # Chamorro Standard Time UTC+10:00
70             # 'DST' => '' # Daylight saving time Depending
71             #'DFT' => '+0100', # AIX specific equivalent of Central European Time UTC+01:00
72             #'EAST' => '-0600', # Easter Island Standard Time UTC-06:00
73             #'EAT' => '+0300', # East Africa Time UTC+03:00
74             #'ECT' => '-0400', # Eastern Caribbean Time (does not recognise DST) UTC-04:00
75             #'ECT' => '-0500', # Ecuador Time UTC-05:00
76             'EDT' => '-0400', # Eastern Daylight Time (North America) UTC-04:00
77             #'EEDT' => '+0300', # Eastern European Daylight Time UTC+03:00
78             #'EEST' => '+0300', # Eastern European Summer Time UTC+03:00
79             #'EET' => '+0200', # Eastern European Time UTC+02:00
80             'EST' => '+0500', # Eastern Standard Time (North America) UTC-05:00
81             #'FJT' => '+1200', # Fiji Time UTC+12:00
82             #'FKST' => '-0400', # Falkland Islands Standard Time UTC-04:00
83             #'GALT' => '-0600', # Galapagos Time UTC-06:00
84             #'GET' => '+0400', # Georgia Standard Time UTC+04:00
85             #'GFT' => '-0300', # French Guiana Time UTC-03:00
86             #'GILT' => '+1200', # Gilbert Island Time UTC+12:00
87             #'GIT' => '-0900', # Gambier Island Time UTC-09:00
88             'GMT' => '+0000', # Greenwich Mean Time UTC
89             #'GST' => '-0200', # South Georgia and the South Sandwich Islands UTC-02:00
90             #'GYT' => '-0400', # Guyana Time UTC-04:00
91             'HADT' => '-0900', # Hawaii-Aleutian Daylight Time UTC-09:00
92             'HAST' => '-1000', # Hawaii-Aleutian Standard Time UTC-10:00
93             #'HKT' => '+0800', # Hong Kong Time UTC+08:00
94             #'HMT' => '+0500', # Heard and McDonald Islands Time UTC+05:00
95             'HST' => '-1000', # Hawaii Standard Time UTC-10:00
96             #'IRKT' => '+0800', # Irkutsk Time UTC+08:00
97             #'IRST' => '+0330', # Iran Standard Time UTC+03:30
98             #'IST' => '+0530', # Indian Standard Time UTC+05:30
99             #'IST' => '+0100', # Irish Summer Time UTC+01:00
100             #'IST' => '+0200', # Israel Standard Time UTC+02:00
101             'JST' => '+0900', # Japan Standard Time UTC+09:00
102             #'KRAT' => '+0700', # Krasnoyarsk Time UTC+07:00
103             #'KST' => '+0900', # Korea Standard Time UTC+09:00
104             #'LHST' => '+1030', # Lord Howe Standard Time UTC+10:30
105             #'LINT' => '+1400', # Line Islands Time UTC+14:00
106             #'MAGT' => '+1100', # Magadan Time UTC+11:00
107             'MDT' => '-0600', # Mountain Daylight Time(North America) UTC-06:00
108             #'MIT' => '-0930', # Marquesas Islands Time UTC-09:30
109             #'MSD' => '+0400', # Moscow Summer Time UTC+04:00
110             #'MSK' => '+0300', # Moscow Standard Time UTC+03:00
111             #'MST' => '+0800', # Malaysian Standard Time UTC+08:00
112             'MST' => '-0700', # Mountain Standard Time(North America) UTC-07:00
113             #'MST' => '+0630', # Myanmar Standard Time UTC+06:30
114             #'MUT' => '+0400', # Mauritius Time UTC+04:00
115             #'NDT' => '-0230', # Newfoundland Daylight Time UTC-02:30
116             #'NFT' => '+1130', # Norfolk Time[1] UTC+11:30
117             #'NPT' => '+0545', # Nepal Time UTC+05:45
118             #'NST' => '-0330', # Newfoundland Standard Time UTC-03:30
119             #'NT' => '-0330', # Newfoundland Time UTC-03:30
120             #'OMST' => '+0600', # Omsk Time UTC+06:00
121             'PDT' => '-0700', # Pacific Daylight Time(North America) UTC-07:00
122             #'PETT' => '+1200', # Kamchatka Time UTC+12:00
123             #'PHOT' => '+1300', # Phoenix Island Time UTC+13:00
124             #'PKT' => '+0500', # Pakistan Standard Time UTC+05:00
125             'PST' => '-0800', # Pacific Standard Time (North America) UTC-08:00
126             #'PST' => '+0800', # Philippine Standard Time UTC+08:00
127             #'RET' => '+0400', # Reunion Time UTC+04:00
128             #'SAMT' => '+0400', # Samara Time UTC+04:00
129             #'SAST' => '+0200', # South African Standard Time UTC+02:00
130             #'SBT' => '+1100', # Solomon Islands Time UTC+11:00
131             #'SCT' => '+0400', # Seychelles Time UTC+04:00
132             #'SLT' => '+0530', # Sri Lanka Time UTC+05:30
133             #'SST' => '-1100', # Samoa Standard Time UTC-11:00
134             #'SST' => '+0800', # Singapore Standard Time UTC+08:00
135             #'TAHT' => '-1000', # Tahiti Time UTC-10:00
136             #'THA' => '+0700', # Thailand Standard Time UTC+07:00
137             'UT' => '-0000', # Coordinated Universal Time UTC
138             'UTC' => '-0000', # Coordinated Universal Time UTC
139             #'UYST' => '-0200', # Uruguay Summer Time UTC-02:00
140             #'UYT' => '-0300', # Uruguay Standard Time UTC-03:00
141             #'VET' => '-0430', # Venezuelan Standard Time UTC-04:30
142             #'VLAT' => '+1000', # Vladivostok Time UTC+10:00
143             #'WAT' => '+0100', # West Africa Time UTC+01:00
144             #'WEDT' => '+0100', # Western European Daylight Time UTC+01:00
145             #'WEST' => '+0100', # Western European Summer Time UTC+01:00
146             #'WET' => '-0000', # Western European Time UTC
147             #'YAKT' => '+0900', # Yakutsk Time UTC+09:00
148             #'YEKT' => '+0500', # Yekaterinburg Time UTC+05:00
149 79     79   555 };
  79         145  
150              
151             sub to_second {
152             # Convert to second
153             # @param [String] value Digit and a unit of time
154             # @return [Integer] n: seconds
155             # 0: 0 or invalid unit of time
156             # @example Get the value of seconds
157             # to_second('1d') #=> 86400
158             # to_second('2h') #=> 7200
159 83     83 1 38085 my $class = shift;
160 83   100     220 my $value = shift || return 0;
161              
162 59         79 state $timeunit = {
163             'o' => ( BASE_D * BASE_Y * 4 ), # Olympiad, 4 years
164             'y' => ( BASE_D * BASE_Y ), # Year, Gregorian Calendar
165             'q' => ( BASE_D * BASE_Y / 4 ), # Quarter, year/4
166             'l' => ( BASE_D * BASE_L ), # Lunar month
167             'f' => ( BASE_D * 14 ), # Fortnight, 2 weeks
168             'w' => ( BASE_D * 7 ), # Week, 604800 seconds
169             'd' => BASE_D, # Day
170             'h' => 3600, # Hour
171             'b' => 86.4, # Beat, Swatch internet time: 1000b = 1d
172             'm' => 60, # Minute,
173             's' => 1, # Second
174             };
175 59         64 state $mathematicalconstant = {
176             'e' => CONST_E,
177             'p' => CONST_P,
178             'g' => CONST_E ** CONST_P,
179             };
180              
181 59         64 my $getseconds = 0;
182 59         248 my $unitoftime = [keys %$timeunit];
183 59         133 my $mathconsts = [keys %$mathematicalconstant];
184              
185 59 100       506 if( $value =~ /\A(\d+|\d+[.]\d+)([@$unitoftime])?\z/o ) {
    100          
186             # 1d, 1.5w
187 7         16 my $n = $1;
188 7   50     19 my $u = $2 // 'd';
189 7         20 $getseconds = $n * $timeunit->{ $u };
190              
191             } elsif( $value =~ /\A(\d+|\d+[.]\d+)?([@$mathconsts])([@$unitoftime])?\z/o ) {
192             # 1pd, 1.5pw
193 5   50     24 my $n = $1 // 1;
194 5   100     19 my $m = $mathematicalconstant->{ $2 } // 0;
195 5   100     16 my $u = $3 // 'd';
196 5         13 $getseconds = $n * $m * $timeunit->{ $u };
197              
198             } else {
199 47         67 $getseconds = 0;
200             }
201 59         281 return $getseconds;
202             }
203              
204             sub monthname {
205             # Month name list
206             # @param [Integer] argv1 Require full name or not
207             # @return [Array, String] Month name list or month name
208             # @example Get the names of each month
209             # monthname() #=> ['Jan', 'Feb', ...]
210             # monthname(1) #=> ['January', 'February', 'March', ...]
211 18     18 0 1959 my $class = shift;
212 18   50     57 my $argv1 = shift // 0;
213 18 100       52 my $value = $argv1 ? 'full' : 'abbr';
214              
215 18 50       44 return @{ MonthName->{ $value } } if wantarray;
  0         0  
216 18         96 return MonthName->{ $value };
217             }
218              
219             sub dayofweek {
220             # List of day of week
221             # @param [Integer] argv1 Require full name
222             # @return [Array, String] List of day of week or day of week
223             # @example Get the names of each day of week
224             # dayofweek() #=> ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
225             # dayofweek(1) #=> ['Sunday', 'Monday', 'Tuesday', ...]
226 2     2 0 2769 my $class = shift;
227 2   50     7 my $argv1 = shift // 0;
228 2 100       6 my $value = $argv1 ? 'full' : 'abbr';
229              
230 2 50       5 return @{ DayOfWeek->{ $value } } if wantarray;
  0         0  
231 2         14 return DayOfWeek->{ $value };
232             }
233              
234             sub parse {
235             # Parse date string; strptime() wrapper
236             # @param [String] argv1 Date string
237             # @return [String] Converted date string
238             # @see http://en.wikipedia.org/wiki/ISO_8601
239             # @see http://www.ietf.org/rfc/rfc3339.txt
240             # @example Parse date string and convert to generic format string
241             # parse("2015-11-03T23:34:45 Tue") #=> Tue, 3 Nov 2015 23:34:45 +0900
242             # parse("Tue, Nov 3 2015 2:2:2") #=> Tue, 3 Nov 2015 02:02:02 +0900
243 2999     2999 1 38223 my $class = shift;
244 2999   50     5392 my $argv1 = shift || return undef;
245              
246 2999         3817 my $datestring = $argv1;
247 2999         9164 $datestring =~ s{[,](\d+)}{, $1}; # Thu,13 -> Thu, 13
248 2999         6334 $datestring =~ s{(\d{1,2}),}{$1}; # Apr 29, -> Apr 29
249 2999         10104 my @timetokens = split(' ', $datestring);
250 2999         4045 my $parseddate = ''; # [String] Canonified Date/Time string
251 2999         3557 my $afternoon1 = 0; # [Integer] After noon flag
252 2999         4009 my $altervalue = {}; # [Hash] To store alternative values
253 2999         11435 my $v = {
254             'Y' => undef, # [Integer] Year
255             'M' => undef, # [String] Month Abbr.
256             'd' => undef, # [Integer] Day
257             'a' => undef, # [String] Day of week, Abbr.
258             'T' => undef, # [String] Time
259             'z' => undef, # [Integer] Timezone offset
260             };
261              
262 2999         4818 for my $p ( @timetokens ) {
263             # Parse each piece of time
264 18837 100 100     78368 if( $p =~ /\A[A-Z][a-z]{2,}[,]?\z/ ) {
    100          
    100          
    100          
    100          
    100          
265             # Day of week or Day of week; Thu, Apr, ...
266 5900 100       17087 $p =~ s/,\z//g if substr($p, -1, 1) eq ','; # "Thu," => "Thu"
267 5900 100       10837 $p = substr($p, 0, 3) if length $p > 3;
268              
269 5900 100       6117 if( grep { $p eq $_ } @{ DayOfWeek->{'abbr'} } ) {
  41300 100       58453  
  5900         10585  
270             # Day of week; Mon, Thu, Sun,...
271 2925         6690 $v->{'a'} = $p;
272              
273 35700         46351 } elsif( grep { $p eq $_ } @{ MonthName->{'abbr'} } ) {
  2975         5648  
274             # Month name abbr.; Apr, May, ...
275 2972         5575 $v->{'M'} = $p;
276             }
277             } elsif( $p =~ /\A\d{1,4}\z/ ) {
278             # Year or Day; 2005, 31, 04, 1, ...
279 5946         11540 $p = int $p;
280 5946 100       9318 if( $p > 31 ) {
281             # The piece is the value of an year
282 2938         5807 $v->{'Y'} = $p;
283              
284             } else {
285             # The piece is the value of a day
286 3008 100       5274 if( $v->{'d'} ) {
287             # 2-digit year?
288 36 50       411 $altervalue->{'Y'} = $p unless $v->{'Y'};
289              
290             } else {
291             # The value is "day"
292 2972         5736 $v->{'d'} = $p;
293             }
294             }
295             } elsif( $p =~ /\A([0-2]\d):([0-5]\d):([0-5]\d)\z/ ||
296             $p =~ /\A(\d{1,2})[-:](\d{1,2})[-:](\d{1,2})\z/ ) {
297             # Time; 12:34:56, 03:14:15, ...
298             # Arrival-Date: 2014-03-26 00-01-19
299 2979 100 100     21986 if( $1 < 24 && $2 < 60 && $3 < 60 ) {
      100        
300             # Valid time format, maybe...
301 2976         16914 $v->{'T'} = sprintf("%02d:%02d:%02d", $1, $2, $3);
302             }
303             } elsif( $p =~ /\A([0-2]\d):([0-5]\d)\z/ ) {
304             # Time; 12:34 => 12:34:00
305 1 50 33     9 if( $1 < 24 && $2 < 60 ) {
306 1         8 $v->{'T'} = sprintf("%02d:%02d:00", $1, $2);
307             }
308             } elsif( $p =~ /\A(\d\d?):(\d\d?)\z/ ) {
309             # Time: 1:4 => 01:04:00
310 1         8 $v->{'T'} = sprintf("%02d:%02d:00", $1, $2);
311              
312             } elsif( $p =~ /\A[APap][Mm]\z/ ) {
313             # AM or PM
314 11         31 $afternoon1 = 1;
315              
316             } else {
317             # Timezone offset and others
318 3999 100       10737 if( $p =~ /\A[-+][01]\d{3}\z/ ) {
    100          
319             # Timezone offset; +0000, +0900, -1000, ...
320 2900   33     9804 $v->{'z'} ||= $p;
321              
322             } elsif( $p =~ /\A[(]?[A-Z]{2,5}[)]?\z/ ) {
323             # Timezone abbreviation; JST, GMT, UTC, ...
324 1065   50     3167 $v->{'z'} ||= __PACKAGE__->abbr2tz($p) || '+0000';
      66        
325              
326             } else {
327             # Other date format
328 34 100       409 if( $p =~ m|\A(\d{4})[-/](\d{1,2})[-/](\d{1,2})\z| ) {
    100          
    100          
    50          
329             # Mail.app(MacOS X)'s faked Bounce, Arrival-Date: 2010-06-18 17:17:52 +0900
330 2         9 $v->{'Y'} = int $1;
331 2         8 $v->{'M'} = MonthName->{'abbr'}->[int($2) - 1];
332 2         5 $v->{'d'} = int $3;
333              
334             } elsif( $p =~ m|\A(\d{4})[-/](\d{1,2})[-/](\d{1,2})T([0-2]\d):([0-5]\d):([0-5]\d)\z| ) {
335             # ISO 8601; 2000-04-29T01:23:45
336 18         97 $v->{'Y'} = int $1;
337 18         80 $v->{'M'} = MonthName->{'abbr'}->[int($2) - 1];
338 18 50       100 $v->{'d'} = int $3 if $3 < 32;
339              
340 18 50 33     181 if( $4 < 24 && $5 < 60 && $6 < 60 ) {
      33        
341 18         119 $v->{'T'} = sprintf("%02d:%02d:%02d", $4, $5, $6);
342             }
343             } elsif( $p =~ m|\A(\d{1,2})/(\d{1,2})/(\d{1,2})\z| ) {
344             # 4/29/01 11:34:45 PM
345 6         36 $v->{'M'} = MonthName->{'abbr'}->[int($1) - 1];
346 6         18 $v->{'d'} = int $2;
347 6         18 $v->{'Y'} = int($3) + 2000;
348 6 50       40 $v->{'Y'} -= 100 if $v->{'Y'} > Time::Piece->new->year() + 1;
349              
350             } elsif( $p =~ m|\A(\d{1,2})[-/](\d{1,2})[-/](\d{4})| ) {
351             # 29-04-2017 22:22
352 0 0       0 $v->{'d'} = int $1 if $1 < 32;
353 0         0 $v->{'M'} = MonthName->{'abbr'}->[int($2) - 1];
354 0         0 $v->{'Y'} = int($3);
355             }
356             }
357             }
358             } # End of while()
359              
360 2999 100 100     9761 if( $v->{'T'} && $afternoon1 ) {
361             # +12
362 11         46 my $t0 = $v->{'T'};
363 11         51 my @t1 = split(':', $v->{'T'});
364 11         61 $v->{'T'} = sprintf("%02d:%02d:%02d", $t1[0] + 12, $t1[1], $t1[2]);
365 11 100       55 $v->{'T'} = $t0 if $t1[0] > 12;
366             }
367              
368 2999   100     5918 $v->{'a'} ||= 'Thu'; # There is no day of week
369 2999 100 100     10538 if( defined $v->{'Y'} && $v->{'Y'} < 200 ) {
370             # 99 -> 1999, 102 -> 2002
371 2         3 $v->{'Y'} += 1900;
372             }
373 2999   66     6799 $v->{'z'} ||= __PACKAGE__->second2tz(Time::Piece->new->tzoffset);
374              
375             # Adjust 2-digit Year
376 2999 100 66     7519 if( exists $altervalue->{'Y'} && ! $v->{'Y'} ) {
377             # Check alternative value(Year)
378 36 50       155 if( $altervalue->{'Y'} >= 82 ) {
379             # SMTP was born in 1982
380 0   0     0 $v->{'Y'} ||= 1900 + $altervalue->{'Y'};
381              
382             } else {
383             # 20XX
384 36   33     188 $v->{'Y'} ||= 2000 + $altervalue->{'Y'};
385             }
386             }
387              
388             # Check each piece
389 2999 100       8677 if( grep { ! defined $_ } values %$v ) {
  17994         29123  
390             # Strange date format
391 5         233 printf(STDERR " ***warning: Strange date format [%s]\n", $datestring);
392 5         45 return undef;
393             }
394              
395 2994 100 100     10893 if( $v->{'Y'} < 1902 || $v->{'Y'} > 2037 ) {
396             # -(2^31) ~ (2^31)
397 2         9 return undef;
398             }
399              
400             # Build date string
401             # Thu, 29 Apr 2004 10:01:11 +0900
402             return sprintf("%s, %s %s %s %s %s",
403 2992         23443 $v->{'a'}, $v->{'d'}, $v->{'M'}, $v->{'Y'}, $v->{'T'}, $v->{'z'});
404             }
405              
406             sub abbr2tz {
407             # Abbreviation -> Tiemzone
408             # @param [String] argv1 Abbr. e.g.) JST, GMT, PDT
409             # @return [String, Undef] +0900, +0000, -0600 or Undef if the argument is
410             # invalid format or not supported abbreviation
411             # @example Get the timezone string of "JST"
412             # abbr2tz('JST') #=> '+0900'
413 87     87 1 490 my $class = shift;
414 87   50     281 my $argv1 = shift || return undef;
415 87         524 return TimeZones->{ $argv1 };
416             }
417              
418             sub tz2second {
419             # Convert to second
420             # @param [String] argv1 Timezone string e.g) +0900
421             # @return [Integer,Undef] n: seconds or Undef it the argument is invalid
422             # format string
423             # @see second2tz
424             # @example Convert '+0900' to seconds
425             # tz2second('+0900') #=> 32400
426 3044     3044 0 30178 my $class = shift;
427 3044   100     8902 my $argv1 = shift || return undef;
428              
429 3018 100       9327 if( $argv1 =~ /\A([-+])(\d)(\d)(\d{2})\z/ ) {
    100          
430 2970         4019 my $ztime = 0;
431 2970         13895 my $digit = {
432             'operator' => $1,
433             'hour-10' => $2,
434             'hour-01' => $3,
435             'minutes' => $4
436             };
437 2970         8383 $ztime += ( $digit->{'hour-10'} * 10 + $digit->{'hour-01'} ) * 3600;
438 2970         5294 $ztime += ( $digit->{'minutes'} * 60 );
439 2970 100       6270 $ztime *= -1 if $digit->{'operator'} eq '-';
440              
441 2970 100       5742 return undef if abs($ztime) > TZ_OFFSET;
442 2968         9995 return $ztime;
443              
444             } elsif( $argv1 =~ /\A[A-Za-z]+\z/ ) {
445 1         22 return __PACKAGE__->tz2second(TimeZones->{ $argv1 });
446              
447             } else {
448 47         163 return undef;
449             }
450             }
451              
452             sub second2tz {
453             # Convert to Timezone string
454             # @param [Integer] argv1 Second to be converted
455             # @return [String] Timezone offset string
456             # @see tz2second
457             # @example Get timezone offset string of specified seconds
458             # second2tz(12345) #=> '+0325'
459 46     46 0 8292 my $class = shift;
460 46   100     439 my $argv1 = shift // return '+0000';
461 45         357 my $digit = { 'operator' => '+' };
462              
463 45 50 66     220 return '' if( ref($argv1) && ref($argv1) ne 'Time::Seconds' );
464 45 100       199 return '' if( abs($argv1) > TZ_OFFSET ); # UTC+14 + 1(DST?)
465 43 100       1546 $digit->{'operator'} = '-' if $argv1 < 0;
466              
467 43         594 $digit->{'hours'} = int(abs($argv1) / 3600);
468 43         726 $digit->{'minutes'} = int((abs($argv1) % 3600) / 60);
469 43         988 return sprintf("%s%02d%02d", $digit->{'operator'}, $digit->{'hours'}, $digit->{'minutes'});
470             }
471              
472             1;
473             __END__