1package Math::Calc::Units::Convert::Date;
2use base 'Math::Calc::Units::Convert::Base';
3use Time::Local qw(timegm);
4use strict;
5use vars qw(%units %pref %ranges %total_unit_map);
6
7my $min_nice_time = timegm(0, 0, 0, 1, 0, 1975-1900);
8my $max_nice_time = timegm(0, 0, 0, 1, 0, 2030-1900);
9
10%units = ();
11%pref = ( default => 1 );
12%ranges = ( timestamp => [ $min_nice_time, $max_nice_time ] );
13
14sub major_pref {
15    return 2;
16}
17
18# sub major_variants {}
19
20# sub variants {}
21
22sub canonical_unit { return 'timestamp'; }
23
24sub unit_map {
25    my ($self) = @_;
26    if (keys %total_unit_map == 0) {
27	%total_unit_map = (%{$self->SUPER::unit_map()}, %units);
28    }
29    return \%total_unit_map;
30}
31
32sub get_ranges {
33    return \%ranges;
34}
35
36sub get_prefs {
37    return \%pref;
38}
39
40use vars qw(@MonthNames);
41BEGIN { @MonthNames = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); }
42sub construct {
43    my ($self, $constructor, $args) = @_;
44
45    # Allow timestamp(1000347142) or timestamp() for the current time
46    if ($constructor eq 'timestamp') {
47        $args = time if $args eq '';
48        return [ $args, { 'timestamp' => 1 } ];
49    }
50
51    return unless $constructor eq 'date';
52
53    # Accept a very limited range of formats.
54
55    # Always assume GMT if not given. Currently, do not handle timezones.
56    $args =~ s/\s+GMT\s+$//;
57
58    my ($Mon, $d, $y, $h, $m, $s, $tz, $M);
59    $tz = 'GMT';
60
61    # Format 1: [Weekday] Mon DD HH:MM:SS [Timezone] YYYY
62    # (as returned by gmtime and the 'date' command)
63    # The weekday is ignored if given. The timezone is currently ignored.
64    if ($args =~ /^((?:\w\w\w\s+)?)
65                   (\w\w\w)\s*
66                   (\d+)\s+
67                   (\d+):(\d+)[:.](\d+)\s+
68                   (\w+)?\s*
69                   (\d\d\d\d)$/x)
70    {
71        (undef, $Mon, $d, $h, $m, $s, $tz, $y) = ($1, $2, $3, $4, $5, $6, $7, $8);
72
73    # Format 2: Mon DD YYYY
74    } elsif ($args =~ /^(\w\w\w)[\s-]*
75                        (\d+)[,\s-]+
76                        (\d\d\d\d)$/x)
77    {
78        ($Mon, $d, $y) = ($1, $2, $3);
79
80    # Format 3: YYYY-MM-DD HH:MM:SS
81    } elsif ($args =~ /^(\d\d\d\d)-(\d+)-(\d+)\s+
82                        (\d+):(\d+)[:.](\d+)$/x)
83    {
84        ($y, $M, $d, $h, $m, $s) = ($1, $2, $3, $4, $5, $6);
85        $M--;
86
87    # Format 4: YYYY-MM-DD
88    } elsif ($args =~ /^(\d\d\d\d)-(\d+)-(\d+)$/) {
89        ($y, $M, $d) = ($1, $2, $3);
90        $M--;
91    } else {
92        die "Unparseable date string '$args'";
93    }
94
95    $h ||= 0;
96    $m ||= 0;
97    $s ||= 0;
98
99    if (defined $Mon) {
100        $M = 0;
101        foreach (@MonthNames) {
102            last if lc($_) eq lc($Mon);
103            $M++;
104        }
105        die "Unparseable month '$Mon'" if $M > 11;
106    }
107
108    if (defined($tz) && $tz ne 'GMT') {
109        warn "Timezones not supported. Assuming GMT.\n";
110    }
111
112    my $timestamp = timegm($s, $m, $h, $d, $M, $y-1900);
113    die "Date '$args' is out of range" if $timestamp == -1;
114    return [ $timestamp, { 'timestamp' => 1 } ];
115}
116
117sub render {
118    my ($self, $mag, $name, $power) = @_;
119    return "\@$mag" if $power != 1;
120    return "\@$mag" if $mag < $min_nice_time;
121    return "\@$mag" if $mag > $max_nice_time;
122    return gmtime($mag) . " (\@$mag)";
123}
124
1251;
126