File Coverage

blib/lib/DateTime/Format/DateManip.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package DateTime::Format::DateManip;
2              
3 2     2   15251 use strict;
  2         3  
  2         63  
4              
5 2     2   7 use vars qw ($VERSION);
  2         3  
  2         131  
6              
7             $VERSION = '0.04';
8              
9 2     2   15 use Carp;
  2         2  
  2         105  
10              
11 2     2   1133 use DateTime;
  0            
  0            
12             use DateTime::Duration;
13              
14             use Date::Manip;
15              
16              
17             # All formats are in the ASCII range so we can safely turn off UTF8 support
18             use bytes;
19              
20             # This takes a Date::Manip string and converts it to a DateTime object
21             # Note that the Date::Manip string just needs to be something that
22             # Date::Manip::ParseDate() can format.
23             # undef is returned if the string can not be converted.
24             sub parse_datetime {
25             my ($class, $dm_date) = @_;
26             croak "Missing DateManip parseable string" unless defined $dm_date;
27              
28             # Get the timezone name and the date information and zome offset from
29             # the Date::Manip string.
30             my ($dm_tz, @bits) = UnixDate($dm_date, qw( %Z %Y %m %d %H %M %S %z ));
31             return undef unless @bits;
32             my @args = merge_lists([qw( year month day hour minute second time_zone )],
33             \@bits);
34              
35             # Construct the DateTime object and use the offset timezone
36             my $dt = DateTime->new(@args);
37              
38             # See if there is a better timezone to use
39             my $dt_tz = $class->get_dt_timezone($dm_tz);
40              
41             # Apply the final time zone
42             if (defined $dt_tz) {
43             $dt->set_time_zone($dt_tz);
44             }
45              
46             return $dt;
47             }
48              
49             # Takes a DateTime object and returns the corresponding Date::Manip string (in
50             # the format returned by ParseDate)
51             sub format_datetime {
52             my ($class, $dt) = @_;
53             croak "Missing DateTime object" unless defined $dt;
54              
55             # Note that we just use the TZ offset since Date::Manip doesn't
56             # store time zone information with the dates but sets it system wide
57             return ParseDate( $dt->strftime("%Y%m%dT%H%M%S %z") );
58             }
59              
60             # Takes a Date::Manip Delta string and returns the corresponding
61             # DateTime::Duration object or undef
62             sub parse_duration {
63             my ($class, $dm_delta) = @_;
64             croak "Missing DateManip parseable delta string" unless defined $dm_delta;
65              
66             my @bits = Delta_Format($dm_delta, 0, qw( %yv %Mv %wv %dv %hv %mv %sv ));
67             return undef unless @bits;
68             my @args = merge_lists([qw( years months weeks days hours minutes seconds )],
69             \@bits);
70            
71             # We have to do this in two phases since Date::Manip handles the sign
72             # for years and months separately from the sign for the rest.
73             # DateTime::Duration assumes that the sign is the same across all
74             # items so we make the inital duration with years and months and add
75             # the second duration (which may be negative) to finish the duration
76             my $dt_dur = DateTime::Duration->new(@args[0..3]); # Year and month
77             $dt_dur->add(@args[4..13]); # The rest
78              
79             return $dt_dur;
80             }
81              
82             # Takes a DateTime::Duration object and returns the corresponding
83             # Date::Manip Delta string (in the format returned by ParseDateDelta)
84             sub format_duration {
85             my ($class, $dt_dur) = @_;
86             croak "Missing DateTime::Duration object" unless defined $dt_dur;
87              
88             # Not all elements are defined (if they can be derived from smaller elements)
89             my %bits = $dt_dur->deltas();
90             my $str = join(":",
91             0, # Years
92             $bits{months},
93             0, # Weeks
94             $bits{days},
95             0, # Hours
96             $bits{minutes},
97             $bits{seconds},
98             );
99             my $dm_dur = ParseDateDelta($str);
100            
101             return $dm_dur;
102             }
103              
104              
105             BEGIN {
106             # Date::Manip to DateTime timezone mapping (where possible)
107             my %TZ_MAP =
108             (
109             # Abbreviations (see http://www.worldtimezone.com/wtz-names/timezonenames.html)
110             # [1] - YST matches worldtimezone.com but not Canada/Yukon
111             # [2] - AT matches worldtimezone.com but not Atlantic/Azores
112             # [3] - City chosen at random from similar matches
113             idlw => "-1200", # International Date Line West (-1200)
114             nt => "-1100", # Nome (-1100) (obs. -1967)
115             hst => "US/Hawaii", # Hawaii Standard (-1000)
116             cat => "-1000", # Central Alaska (-1000) (obs. -1967)
117             ahst => "-1000", # Alaska-Hawaii Standard (-1000) (obs. 1967-1983)
118             akst => "US/Alaska", # Alaska Standard (-0900)
119             yst => "-0900", # Yukon Standard (-0900) [1]
120             hdt => "-0900", # Hawaii Daylight (-0900) (until 1947?)
121             akdt => "US/Alaska", # Alaska Daylight (-0800)
122             ydt => "-0800", # Yukon Daylight (-0900) [1]
123             pst => "US/Pacific", # Pacific Standard (-0800)
124             pdt => "US/Pacific", # Pacific Daylight (-0700)
125             mst => "US/Mountain", # Mountain Standard (-0700)
126             mdt => "US/Mountain", # Mountain Daylight (-0600)
127             cst => "US/Central", # Central Standard (-0600)
128             cdt => "US/Central", # Central Daylight (-0500)
129             est => "US/Eastern", # Eastern Standard (-0500)
130             sat => "-0400", # Chile (-0400)
131             edt => "US/Eastern", # Eastern Daylight (-0400)
132             ast => "Canada/Atlantic", # Atlantic Standard (-0400)
133             #nst => "Canada/Newfoundland", # Newfoundland Standard (-0300) nst=North Sumatra +0630
134             nft => "Canada/Newfoundland", # Newfoundland (-0330)
135             #gst => "-0300", # Greenland Standard (-0300) gst=Guam Standard +1000
136             #bst => "Brazil/East", # Brazil Standard (-0300) bst=British Summer +0100
137             adt => "Canada/Atlantic", # Atlantic Daylight (-0300)
138             ndt => "Canada/Newfoundland", # Newfoundland Daylight (-0230)
139             at => "-0200", # Azores (-0200) [2]
140             wat => "Africa/Bangui", # West Africa (-0100) [3]
141             gmt => "Europe/London", # Greenwich Mean (+0000)
142             ut => "Etc/Universal", # Universal (+0000)
143             utc => "UTC", # Universal (Coordinated) (+0000)
144             wet => "Europe/Lisbon", # Western European (+0000) [3]
145             west => "Europe/Lisbon", # Alias for Western European (+0000) [3]
146             cet => "Europe/Madrid", # Central European (+0100)
147             fwt => "Europe/Paris", # French Winter (+0100)
148             met => "Europe/Brussels", # Middle European (+0100)
149             mez => "Europe/Berlin", # Middle European (+0100)
150             mewt => "Europe/Brussels", # Middle European Winter (+0100)
151             swt => "Europe/Stockholm", # Swedish Winter (+0100)
152             bst => "Europe/London", # British Summer (+0100) bst=Brazil standard -0300
153             gb => "Europe/London", # GMT with daylight savings (+0100)
154             eet => "Europe/Bucharest", # Eastern Europe, USSR Zone 1 (+0200)
155             cest => "Europe/Madrid", # Central European Summer (+0200)
156             fst => "Europe/Paris", # French Summer (+0200)
157             # ist => "Asia/Jerusalem", # Israel standard (+0200) (duplicate of Indian)
158             mest => "Europe/Brussels", # Middle European Summer (+0200)
159             mesz => "Europe/Berlin", # Middle European Summer (+0200)
160             metdst => "Europe/Brussels", # An alias for mest used by HP-UX (+0200)
161             sast => "Africa/Johannesburg", # South African Standard (+0200)
162             sst => "Europe/Stockholm", # Swedish Summer (+0200) sst=South Sumatra +0700
163             bt => "+0300", # Baghdad, USSR Zone 2 (+0300)
164             eest => "Europe/Bucharest", # Eastern Europe Summer (+0300)
165             eetedt => "Europe/Bucharest", # Eastern Europe, USSR Zone 1 (+0300)
166             # idt => "Asia/Jerusalem", # Israel Daylight (+0300) [Jerusalem doesn't honor DST)
167             msk => "Europe/Moscow", # Moscow (+0300)
168             it => "Asia/Tehran", # Iran (+0330)
169             zp4 => "+0400", # USSR Zone 3 (+0400)
170             msd => "Europe/Moscow", # Moscow Daylight (+0400)
171             zp5 => "+0500", # USSR Zone 4 (+0500)
172             ist => "Asia/Calcutta", # Indian Standard (+0530)
173             zp6 => "+0600", # USSR Zone 5 (+0600)
174             nst => "+0630", # North Sumatra (+0630) nst=Newfoundland Std -0330
175             #sst => "+0700", # South Sumatra, USSR Zone 6 sst=Swedish Summer +0200
176             hkt => "Asia/Hong_Kong", # Hong Kong (+0800)
177             sgt => "Asia/Singapore", # Singapore (+0800)
178             cct => "Asia/Shanghai", # China Coast, USSR Zone 7 (+0800)
179             awst => "Australia/West", # West Australian Standard (+0800)
180             wst => "Australia/West", # West Australian Standard (+0800)
181             pht => "Asia/Manila", # Asia Manila (+0800)
182             kst => "Asia/Seoul", # Republic of Korea (+0900)
183             jst => "Asia/Tokyo", # Japan Standard, USSR Zone 8 (+0900)
184             rok => "ROK", # Republic of Korea (+0900)
185             cast => "Australia/South", # Central Australian Standard (+0930)
186             east => "Australia/Victoria", # Eastern Australian Standard (+1000)
187             gst => "Pacific/Guam", # Guam Standard, USSR Zone 9 gst=Greenland Std -0300
188             cadt => "Australia/South", # Central Australian Daylight (+1030)
189             eadt => "Australia/Victoria", # Eastern Australian Daylight (+1100)
190             idle => "+1200", # International Date Line East
191             nzst => "Pacific/Auckland", # New Zealand Standard
192             nzt => "Pacific/Auckland", # New Zealand
193             nzdt => "Pacific/Auckland", # New Zealand Daylight
194            
195             # US Military Zones
196             z => "+0000",
197             a => "+0100",
198             b => "+0200",
199             c => "+0300",
200             d => "+0400",
201             e => "+0500",
202             f => "+0600",
203             g => "+0700",
204             h => "+0800",
205             i => "+0900",
206             k => "+1000",
207             l => "+1100",
208             m => "+1200",
209             n => "-0100",
210             o => "-0200",
211             p => "-0300",
212             q => "-0400",
213             r => "-0500",
214             s => "-0600",
215             t => "-0700",
216             u => "-0800",
217             v => "-0900",
218             w => "-1000",
219             x => "-1100",
220             y => "-1200",
221             );
222              
223             # Return the DateTime timezone corresponding to the given Date::Manip timezone or
224             # return undef if there is no match.
225             sub get_dt_timezone {
226             my ($class, $dm_tz) = @_;
227              
228             # Work out the time zone that Date::Manip was using and try to reproduce it
229             # in DateTime
230             my $dt_tz = $dm_tz;
231             if ($dm_tz =~ m{/}) {
232             # Don't change it since it is in the complete form already
233             # (e.g. America/New_York)
234             }
235             elsif ($dm_tz =~ m/^[-+]\d+$/) {
236             # It is an offset, leave it alone (e.g. -0500)
237             }
238             else {
239             # Look it up
240             my $lc_tz = lc $dm_tz;
241             $dt_tz = $TZ_MAP{$lc_tz};
242             }
243              
244             return $dt_tz;
245             }
246             }
247              
248             # Take a list of keys and a list of values and insersperse them and
249             # return the result
250             sub merge_lists {
251             my ($keys, $values) = @_;
252             die "Length mismatch" unless @$keys == @$values;
253            
254             # Add the argument names to the values
255             my @result;
256             for (my $i = 0; $i < @$keys; $i++) {
257             push @result, $keys->[$i] => $values->[$i];
258             }
259            
260             return @result;
261             }
262              
263             1;
264              
265              
266             __END__