File Coverage

blib/lib/Astro/FITS/HdrTrans/UKIRTDB.pm
Criterion Covered Total %
statement 127 201 63.1
branch 34 90 37.7
condition 10 48 20.8
subroutine 23 27 85.1
pod 19 19 100.0
total 213 385 55.3


line stmt bran cond sub pod time code
1             package Astro::FITS::HdrTrans::UKIRTDB;
2              
3             =head1 NAME
4              
5             Astro::FITS::HdrTrans::UKIRTDB - UKIRT Database Table translations
6              
7             =head1 SYNOPSIS
8              
9             %generic_headers = translate_from_FITS(\%FITS_headers, \@header_array);
10              
11             %FITS_headers = transate_to_FITS(\%generic_headers, \@header_array);
12              
13             =head1 DESCRIPTION
14              
15             Converts information contained in UKIRTDB FITS headers to and from
16             generic headers. See Astro::FITS::HdrTrans for a list of generic
17             headers.
18              
19             =cut
20              
21 10     10   53342084 use 5.006;
  10         49  
22 10     10   65 use warnings;
  10         41  
  10         907  
23 10     10   117 use strict;
  10         34  
  10         299  
24 10     10   63 use Carp;
  10         23  
  10         1180  
25              
26 10     10   1131 use Time::Piece;
  10         18376  
  10         138  
27              
28             # Inherit from Base
29 10     10   1224 use base qw/ Astro::FITS::HdrTrans::JAC /;
  10         27  
  10         32150  
30              
31             # Note that we use %02 not %03 because of historical reasons
32             our $VERSION = "1.66";
33              
34             # for a constant mapping, there is no FITS header, just a generic
35             # header that is constant
36             my %CONST_MAP = (
37             COORDINATE_UNITS => 'degrees',
38             );
39              
40             # NULL mappings used to override base class implementations
41             my @NULL_MAP = ();
42              
43             # unit mapping implies that the value propogates directly
44             # to the output with only a keyword name change
45              
46             my %UNIT_MAP = (
47             AIRMASS_START => "AMSTART",
48             AIRMASS_END => "AMEND",
49             CAMERA => "CAMLENS",
50             CAMERA_NUMBER => "CAMNUM",
51             CONFIGURATION_INDEX => "CNFINDEX",
52             DEC_BASE => "DECBASE",
53             DEC_SCALE => "PIXELSIZ",
54             DEC_TELESCOPE_OFFSET => "DECOFF",
55             DETECTOR_READ_TYPE => "MODE",
56             DR_GROUP => "GRPNUM",
57             DR_RECIPE => "RECIPE",
58             EQUINOX => "EQUINOX",
59             FILTER => "FILTER",
60             FILENAME => "FILENAME",
61             GAIN => "DEPERDN",
62             GRATING_DISPERSION => "GDISP",
63             GRATING_ORDER => "GORDER",
64             INSTRUMENT => "INSTRUME",
65             NUMBER_OF_COADDS => 'NEXP',
66             NUMBER_OF_EXPOSURES => "NEXP",
67             OBJECT => "OBJECT",
68             OBSERVATION_MODE => "INSTMODE",
69             OBSERVATION_NUMBER => "RUN",
70             OBSERVATION_TYPE => "OBSTYPE",
71             PROJECT => "PROJECT",
72             RA_SCALE => "PIXELSIZ",
73             RA_TELESCOPE_OFFSET => "RAOFF",
74             TELESCOPE => "TELESCOP",
75             WAVEPLATE_ANGLE => "WPLANGLE",
76             Y_BASE => "DECBASE",
77             X_DIM => "DCOLUMNS",
78             Y_DIM => "DROWS",
79             X_OFFSET => "RAOFF",
80             Y_OFFSET => "DECOFF",
81             X_SCALE => "PIXELSIZ",
82             Y_SCALE => "PIXELSIZ",
83             X_LOWER_BOUND => "RDOUT_X1",
84             X_UPPER_BOUND => "RDOUT_X2",
85             Y_LOWER_BOUND => "RDOUT_Y1",
86             Y_UPPER_BOUND => "RDOUT_Y2"
87             );
88              
89              
90             # Create the translation methods
91             __PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP, \@NULL_MAP );
92              
93              
94             =head1 METHODS
95              
96             =over 4
97              
98             =item B<can_translate>
99              
100             Determine if this class can handle the translation. Returns true
101             if the TELESCOP is "UKIRT" and there is a "FILENAME" key and
102             a "RAJ2000" key. These keywords allow the DB results to be disambiguated
103             from the actual file headers.
104              
105             $cando = $class->can_translate( \%hdrs );
106              
107             =cut
108              
109             sub can_translate {
110 20     20 1 63 my $self = shift;
111 20         54 my $FITS_headers = shift;
112 20 50 100     181 if (exists $FITS_headers->{TELESCOP}
      100        
      66        
113             && $FITS_headers->{TELESCOP} =~ /UKIRT/
114             && exists $FITS_headers->{FILENAME}
115             && exists $FITS_headers->{RAJ2000}) {
116 1         6 return 1;
117             }
118             }
119              
120             =back
121              
122             =head1 COMPLEX CONVERSIONS
123              
124             These methods are more complicated than a simple mapping. We have to
125             provide both from- and to-FITS conversions All these routines are
126             methods and the to_ routines all take a reference to a hash and return
127             the translated value (a many-to-one mapping) The from_ methods take a
128             reference to a generic hash and return a translated hash (sometimes
129             these are many-to-many)
130              
131             =over 4
132              
133             =item B<to_INST_DHS>
134              
135             Sets the INST_DHS header.
136              
137             =cut
138              
139             sub to_INST_DHS {
140 1     1 1 3 my $self = shift;
141 1         5 my $FITS_headers = shift;
142 1         2 my $return;
143              
144 1 50       5 if ( exists( $FITS_headers->{DHSVER} ) ) {
145 0         0 $FITS_headers->{DHSVER} =~ /^(\w+)/;
146 0         0 my $dhs = uc($1);
147 0         0 $return = $FITS_headers->{INSTRUME} . "_$dhs";
148             } else {
149 1         3 my $dhs = "UKDHS";
150 1         5 $return = $FITS_headers->{INSTRUME} . "_$dhs";
151             }
152              
153 1         4 return $return;
154              
155             }
156              
157             =item B<to_EXPOSURE_TIME>
158              
159             Converts either the C<EXPOSED> or C<DEXPTIME> FITS header into
160             the C<EXPOSURE_TIME> generic header.
161              
162             =cut
163              
164             sub to_EXPOSURE_TIME {
165 1     1 1 4 my $self = shift;
166 1         3 my $FITS_headers = shift;
167 1         5 my $return;
168              
169 1 50 33     9 if ( exists( $FITS_headers->{'EXPOSED'} ) && defined( $FITS_headers->{'EXPOSED'} ) ) {
    0 0        
    0 0        
170 1         4 $return = $FITS_headers->{'EXPOSED'};
171             } elsif ( exists( $FITS_headers->{'DEXPTIME'} ) && defined( $FITS_headers->{'DEXPTIME'} ) ) {
172 0         0 $return = $FITS_headers->{'DEXPTIME'};
173             } elsif ( exists( $FITS_headers->{'EXP_TIME'} ) && defined( $FITS_headers->{'EXP_TIME'} ) ) {
174 0         0 $return = $FITS_headers->{'EXP_TIME'};
175             }
176 1         5 return $return;
177             }
178              
179             =item B<to_COORDINATE_TYPE>
180              
181             Converts the C<EQUINOX> FITS header into B1950 or J2000, depending
182             on equinox value, and sets the C<COORDINATE_TYPE> generic header.
183              
184             =cut
185              
186             sub to_COORDINATE_TYPE {
187 1     1 1 3 my $self = shift;
188 1         3 my $FITS_headers = shift;
189 1         3 my $return;
190 1 50       5 if (exists($FITS_headers->{EQUINOX})) {
191 1 50       10 if ($FITS_headers->{EQUINOX} =~ /1950/) {
    50          
192 0         0 $return = "B1950";
193             } elsif ($FITS_headers->{EQUINOX} =~ /2000/) {
194 1         4 $return = "J2000";
195             }
196             }
197 1         5 return $return;
198             }
199              
200             =item B<to_GRATING_NAME>
201              
202             =cut
203              
204             sub to_GRATING_NAME {
205 1     1 1 5 my $self = shift;
206 1         2 my $FITS_headers = shift;
207 1         2 my $return;
208 1 50       23 if (exists($FITS_headers->{GRATING})) {
    50          
209 0         0 $return = $FITS_headers->{GRATING};
210             } elsif (exists($FITS_headers->{GRISM})) {
211 1         4 $return = $FITS_headers->{GRISM};
212             }
213 1         4 return $return;
214             }
215              
216             =item B<to_GRATING_WAVELENGTH>
217              
218             =cut
219              
220             sub to_GRATING_WAVELENGTH {
221 1     1 1 3 my $self = shift;
222 1         2 my $FITS_headers = shift;
223 1         3 my $return;
224 1 50       7 if (exists($FITS_headers->{GLAMBDA})) {
    50          
225 0         0 $return = $FITS_headers->{GLAMBDA};
226             } elsif (exists($FITS_headers->{CENWAVL})) {
227 0         0 $return = $FITS_headers->{CENWAVL};
228             }
229 1         4 return $return;
230             }
231              
232             =item B<to_SLIT_ANGLE>
233              
234             Converts either the C<SANGLE> or the C<SLIT_PA> header into the C<SLIT_ANGLE>
235             generic header.
236              
237             =cut
238              
239             sub to_SLIT_ANGLE {
240 1     1 1 3 my $self = shift;
241 1         3 my $FITS_headers = shift;
242 1         2 my $return;
243 1 50       8 if (exists($FITS_headers->{'SANGLE'})) {
    50          
244 0         0 $return = $FITS_headers->{'SANGLE'};
245             } elsif (exists($FITS_headers->{'SLIT_PA'} )) {
246 0         0 $return = $FITS_headers->{'SLIT_PA'};
247             }
248 1         4 return $return;
249              
250             }
251              
252             =item B<to_SLIT_NAME>
253              
254             Converts either the C<SLIT> or the C<SLITNAME> header into the C<SLIT_NAME>
255             generic header.
256              
257             =cut
258              
259             sub to_SLIT_NAME {
260 1     1 1 3 my $self = shift;
261 1         3 my $FITS_headers = shift;
262 1         3 my $return;
263 1 50       6 if (exists($FITS_headers->{'SLIT'})) {
    50          
264 0         0 $return = $FITS_headers->{'SLIT'};
265             } elsif (exists($FITS_headers->{'SLITNAME'} )) {
266 1         3 $return = $FITS_headers->{'SLITNAME'};
267             }
268 1         4 return $return;
269              
270             }
271              
272             =item B<to_SPEED_GAIN>
273              
274             =cut
275              
276             sub to_SPEED_GAIN {
277 1     1 1 3 my $self = shift;
278 1         3 my $FITS_headers = shift;
279 1         3 my $return;
280              
281 1 50       23 if ( exists( $FITS_headers->{'SPD_GAIN'} ) ) {
    50          
282 0         0 $return = $FITS_headers->{'SPD_GAIN'};
283             } elsif ( exists( $FITS_headers->{'WAVEFORM'} ) ) {
284 1 50       6 if ( $FITS_headers->{'WAVEFORM'} =~ /thermal/i ) {
285 0         0 $return = 'thermal';
286             } else {
287 1         15 $return = 'normal';
288             }
289             }
290 1         3 return $return;
291             }
292              
293             =item B<to_STANDARD>
294              
295             Converts either the C<STANDARD> header (if it exists) or uses the
296             C<OBJECT> or C<RECIPE> headers to determine if an observation is of a
297             standard. If the C<OBJECT> header starts with either B<BS> or B<FS>,
298             I<or> the DR recipe contains the word STANDARD, it is assumed to be a
299             standard.
300              
301             =cut
302              
303             sub to_STANDARD {
304 1     1 1 2 my $self = shift;
305 1         2 my $FITS_headers = shift;
306              
307             # Set false as default so we do not have to repeat this in the logic
308             # below (could just use undef == false)
309 1         3 my $return = 0; # default false
310              
311 1 50 33     10 if ( exists( $FITS_headers->{'STANDARD'} ) &&
    0 0        
      0        
      0        
312             length( $FITS_headers->{'STANDARD'} . "") > 0 ) {
313              
314 1 50       9 if ($FITS_headers->{'STANDARD'} =~ /^[tf]$/i) {
    50          
315             # Raw header read from FITS header
316 0         0 $return = (uc($FITS_headers->{'STANDARD'}) eq 'T');
317             } elsif ($FITS_headers->{'STANDARD'} =~ /^[01]$/) {
318             # Translated header either so a true logical
319 1         3 $return = $FITS_headers->{'STANDARD'};
320             }
321              
322             } elsif ( ( exists $FITS_headers->{OBJECT} &&
323             $FITS_headers->{'OBJECT'} =~ /^[bf]s/i ) ||
324             ( exists( $FITS_headers->{'RECIPE'} ) &&
325             $FITS_headers->{'RECIPE'} =~ /^standard/i
326             )) {
327             # Either we have an object with name prefix of BS or FS or
328             # our recipe looks suspiciously like a standard.
329 0         0 $return = 1;
330              
331             }
332              
333 1         3 return $return;
334              
335             }
336              
337             =item B<to_UTDATE>
338              
339             =cut
340              
341             sub to_UTDATE {
342 1     1 1 3 my $self = shift;
343 1         4 my $FITS_headers = shift;
344 1         2 my $return;
345              
346 1 50       14 if ( exists( $FITS_headers->{'UT_DATE'} ) ) {
347 1         3 my $datestr = $FITS_headers->{'UT_DATE'};
348 1         6 $return = _parse_date($datestr);
349 1 50       4 die "Error parsing date \"$datestr\"" unless defined $return;
350 1         5 $return = $return->strftime('%Y%m%d');
351             }
352              
353 1         204 return $return;
354              
355             }
356              
357             =item B<to_UTSTART>
358              
359             Strips the optional 'Z' from the C<DATE-OBS> header, or if that header does
360             not exist, combines the C<UT_DATE> and C<RUTSTART> headers into a unified
361             C<UTSTART> header.
362              
363             =cut
364              
365             sub to_UTSTART {
366 2     2 1 5 my $self = shift;
367 2         6 my $FITS_headers = shift;
368 2         5 my $return;
369              
370 2 50 0     40 if ( exists( $FITS_headers->{'DATE_OBS'} ) ) {
    0 0        
      0        
371 2         8 my $dateobs = $FITS_headers->{'DATE_OBS'};
372 2         9 $return = $self->_parse_iso_date( $dateobs );
373             } elsif (exists($FITS_headers->{'UT_DATE'}) && defined($FITS_headers->{'UT_DATE'}) &&
374             exists($FITS_headers->{'RUTSTART'}) && defined( $FITS_headers->{'RUTSTART'} ) ) {
375             # Use the default UTDATE translation but insert "-" for ISO parsing
376 0         0 my $ut = $self->to_UTDATE($FITS_headers);
377 0         0 $ut = join("-", substr($ut,0,4), substr($ut,4,2), substr($ut,6,2));
378 0         0 my $hour = int($FITS_headers->{'RUTSTART'});
379 0         0 my $minute = int( ( $FITS_headers->{'RUTSTART'} - $hour ) * 60 );
380 0         0 my $second = int( ( ( ( $FITS_headers->{'RUTSTART'} - $hour ) * 60) - $minute ) * 60 );
381 0         0 $return = $self->_parse_iso_date( $ut . "T$hour:$minute:$second" );
382             }
383              
384 2         7 return $return;
385             }
386              
387             =item B<from_UTSTART>
388              
389             Converts the C<UTSTART> generic header into C<UT_DATE>, C<RUTSTART>,
390             and C<DATE-OBS> database headers.
391              
392             =cut
393              
394             sub from_UTSTART {
395 0     0 1 0 my $self = shift;
396 0         0 my $generic_headers = shift;
397 0         0 my %return_hash;
398 0 0       0 if (exists($generic_headers->{UTSTART})) {
399 0         0 my $t = _parse_date( $generic_headers->{'UTSTART'} );
400 0         0 my $month = $t->month;
401 0         0 $month =~ /^(.{3})/;
402 0         0 $month = $1;
403 0         0 $return_hash{'UT_DATE'} = $month . " " . $t->mday . " " . $t->year;
404 0         0 $return_hash{'RUTSTART'} = $t->hour + ($t->min / 60) + ($t->sec / 3600);
405 0         0 $return_hash{'DATE_OBS'} = $generic_headers->{'UTSTART'};
406             }
407 0         0 return %return_hash;
408             }
409              
410             =item B<to_UTEND>
411              
412             Strips the optional 'Z' from the C<DATE-END> header, or if that header does
413             not exist, combines the C<UT_DATE> and C<RUTEND> headers into a unified
414             C<UTEND> header.
415              
416             =cut
417              
418             sub to_UTEND {
419 1     1 1 3 my $self = shift;
420 1         14 my $FITS_headers = shift;
421 1         3 my $return;
422              
423 1 50 0     5 if ( exists( $FITS_headers->{'DATE_END'} ) ) {
    0 0        
      0        
424 1         9 my $dateend = $FITS_headers->{'DATE_END'};
425 1         5 $return = $self->_parse_iso_date( $dateend );
426             } elsif (exists($FITS_headers->{'UT_DATE'}) && defined($FITS_headers->{'UT_DATE'}) &&
427             exists($FITS_headers->{'RUTEND'}) && defined( $FITS_headers->{'RUTEND'} ) ) {
428             # Use the default UTDATE translation but insert "-" for ISO parsing
429 0         0 my $ut = $self->to_UTDATE($FITS_headers);
430 0         0 $ut = join("-", substr($ut,0,4), substr($ut,4,2), substr($ut,6,2));
431 0         0 my $hour = int($FITS_headers->{'RUTEND'});
432 0         0 my $minute = int( ( $FITS_headers->{'RUTEND'} - $hour ) * 60 );
433 0         0 my $second = int( ( ( ( $FITS_headers->{'RUTEND'} - $hour ) * 60) - $minute ) * 60 );
434 0         0 $return = $self->_parse_iso_date( $ut . "T$hour:$minute:$second" );
435             }
436              
437 1         3 return $return;
438             }
439              
440             =item B<from_UTEND>
441              
442             Converts the C<UTEND> generic header into C<UT_DATE>, C<RUTEND>
443             and C<DATE-END> database headers.
444              
445             =cut
446              
447             sub from_UTEND {
448 0     0 1 0 my $self = shift;
449 0         0 my $generic_headers = shift;
450 0         0 my %return_hash;
451 0 0       0 if (exists($generic_headers->{UTEND})) {
452 0         0 my $t = _parse_date( $generic_headers->{'UTEND'} );
453 0         0 my $month = $t->month;
454 0         0 $month =~ /^(.{3})/;
455 0         0 $month = $1;
456 0         0 $return_hash{'UT_DATE'} = $month . " " . $t->mday . " " . $t->year;
457 0         0 $return_hash{'RUTEND'} = $t->hour + ($t->min / 60) + ($t->sec / 3600);
458 0         0 $return_hash{'DATE_END'} = $generic_headers->{'UTEND'};
459             }
460 0         0 return %return_hash;
461             }
462              
463             =item B<to_X_BASE>
464              
465             Converts the decimal hours in the FITS header C<RABASE> into
466             decimal degrees for the generic header C<X_BASE>.
467              
468             =cut
469              
470             sub to_X_BASE {
471 1     1 1 3 my $self = shift;
472 1         3 my $FITS_headers = shift;
473 1         2 my $return;
474 1 50       5 if (exists($FITS_headers->{RABASE})) {
475 1         3 $return = $FITS_headers->{RABASE} * 15;
476             }
477 1         3 return $return;
478             }
479              
480             =item B<from_X_BASE>
481              
482             Converts the decimal degrees in the generic header C<X_BASE>
483             into decimal hours for the FITS header C<RABASE>.
484              
485             =cut
486              
487             sub from_X_BASE {
488 0     0 1 0 my $self = shift;
489 0         0 my $generic_headers = shift;
490 0         0 my %return_hash;
491 0 0       0 if (exists($generic_headers->{X_BASE})) {
492 0         0 $return_hash{'RABASE'} = $generic_headers->{X_BASE} / 15;
493             }
494 0         0 return %return_hash;
495             }
496              
497             =item B<to_RA_BASE>
498              
499             Converts the decimal hours in the FITS header C<RABASE> into
500             decimal degrees for the generic header C<RA_BASE>.
501              
502             =cut
503              
504             sub to_RA_BASE {
505 1     1 1 4 my $self = shift;
506 1         3 my $FITS_headers = shift;
507 1         2 my $return;
508 1 50       5 if (exists($FITS_headers->{RABASE})) {
509 1         8 $return = $FITS_headers->{RABASE} * 15;
510             }
511 1         4 return $return;
512             }
513              
514             =item B<from_RA_BASE>
515              
516             Converts the decimal degrees in the generic header C<RA_BASE>
517             into decimal hours for the FITS header C<RABASE>.
518              
519             =cut
520              
521             sub from_RA_BASE {
522 0     0 1 0 my $self = shift;
523 0         0 my $generic_headers = shift;
524 0         0 my %return_hash;
525 0 0       0 if (exists($generic_headers->{RA_BASE})) {
526 0         0 $return_hash{'RABASE'} = $generic_headers->{RA_BASE} / 15;
527             }
528 0         0 return %return_hash;
529             }
530              
531             =back
532              
533             =head1 INTERNAL METHODS
534              
535             =over 4
536              
537             =item B<_fix_dates>
538              
539             Handle the case where DATE_OBS and/or DATE_END are given, and convert
540             them into DATE-OBS and/or DATE-END.
541              
542             =cut
543              
544             sub _fix_dates {
545 1     1   5 my ( $class, $FITS_headers ) = @_;
546              
547 1 50       7 if( defined( $FITS_headers->{'DATE_OBS'} ) ) {
548 1         10 $FITS_headers->{'DATE-OBS'} = $class->_parse_iso_date( $FITS_headers->{'DATE_OBS'} );
549             }
550 1 50       6 if( defined( $FITS_headers->{'DATE_END'} ) ) {
551 1         5 $FITS_headers->{'DATE-END'} = $class->_parse_iso_date( $FITS_headers->{'DATE_END'} );
552             }
553              
554             }
555              
556             =item B<_parse_date>
557              
558             Parses a string as a date. Returns a C<Time::Piece> object.
559              
560             $time = _parse_date( $date );
561              
562             Returns C<undef> if the time could not be parsed.
563             Returns the object unchanged if the argument is already a C<Time::Piece>.
564              
565             It will also recognize a MySQL style date: '2002-03-15 07:04:00'
566             and a simple YYYYMMDD.
567              
568             The date is assumed to be in UT.
569              
570             =cut
571              
572             sub _parse_date {
573 1     1   2 my $date = shift;
574              
575             # If we already have a Time::Piece return
576 1 50       8 return bless $date, "Time::Piece"
577             if UNIVERSAL::isa( $date, "Time::Piece");
578              
579             # We can use Time::Piece->strptime but it requires an exact
580             # format rather than working it out from context (and we don't
581             # want an additional requirement on Date::Manip or something
582             # since Time::Piece is exactly what we want for Astro::Coords)
583             # Need to fudge a little
584              
585 1         3 my $format;
586              
587             # Need to disambiguate ISO date from MySQL date
588 1 50       9 if ($date =~ /\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/) {
    50          
    50          
589             # MySQL
590 0         0 $format = '%Y-%m-%d %T';
591              
592             } elsif ($date =~ /\d\d\d\d-\d\d-\d\d/) {
593             # ISO
594              
595             # All arguments should have a day, month and year
596 0         0 $format = "%Y-%m-%d";
597              
598             # Now check for time
599 0 0       0 if ($date =~ /T/) {
600             # Date and time
601             # Now format depends on the number of colons
602 0         0 my $n = ( $date =~ tr/:/:/ );
603 0 0       0 $format .= "T" . ($n == 2 ? "%T" : "%R");
604             }
605             } elsif ($date =~ /^\d\d\d\d\d\d\d\d\b/) {
606             # YYYYMMDD format
607 0         0 $format = "%Y%m%d";
608             } else {
609             # Allow Sybase date for compatability.
610             # Mar 15 2002 7:04AM
611 1         3 $format = "%b %d %Y %I:%M%p";
612              
613             }
614              
615             # Now parse
616             # Note that this time is treated as "local" rather than "gm"
617 1         2 my $time = eval { Time::Piece->strptime( $date, $format ); };
  1         7  
618 1 50       44 if ($@) {
619 0         0 return undef;
620             } else {
621             # Note that the above constructor actually assumes the date
622             # to be parsed is a local time not UTC. To switch to UTC
623             # simply get the epoch seconds and the timezone offset
624             # and run gmtime
625             # Sometime around v1.07 of Time::Piece the behaviour changed
626             # to return UTC rather than localtime from strptime!
627             # The joys of backwards compatibility.
628 1 50       4 if ($time->[Time::Piece::c_islocal]) {
629 0         0 my $tzoffset = $time->tzoffset;
630 0         0 my $epoch = $time->epoch;
631 0         0 $time = gmtime( $epoch + $tzoffset->seconds );
632             }
633              
634             }
635              
636 1         4 return $time;
637             }
638              
639             =back
640              
641             =head1 SEE ALSO
642              
643             C<Astro::FITS::HdrTrans>, C<Astro::FITS::HdrTrans::UKIRT>,
644             C<Astro::FITS::HdrTrans::Base>.
645              
646             =head1 AUTHORS
647              
648             Brad Cavanagh E<lt>b.cavanagh@jach.hawaii.eduE<gt>,
649             Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>
650              
651             =head1 COPYRIGHT
652              
653             Copyright (C) 2007-2008 Science and Technology Facilities Council.
654             Copyright (C) 2002-2005 Particle Physics and Astronomy Research Council.
655             All Rights Reserved.
656              
657             This program is free software; you can redistribute it and/or modify it under
658             the terms of the GNU General Public License as published by the Free Software
659             Foundation; either version 2 of the License, or (at your option) any later
660             version.
661              
662             This program is distributed in the hope that it will be useful,but WITHOUT ANY
663             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
664             PARTICULAR PURPOSE. See the GNU General Public License for more details.
665              
666             You should have received a copy of the GNU General Public License along with
667             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
668             Place,Suite 330, Boston, MA 02111-1307, USA
669              
670             =cut
671              
672             1;