home *** CD-ROM | disk | FTP | other *** search
- package RISCOS::Time;
-
- use RISCOS::SWI;
- use Carp;
- #use SelfLoader;
- require Exporter;
- use strict;
- use vars qw (@ISA @EXPORT_OK $VERSION $time2utc $time2local $time2_mask
- $s_1900to1970 $seconds_per_day $cs_1900to1970_256 $cs_per_day_256
- $start $standard_date_and_time $standard_date_and_time_mask);
-
- @ISA = qw(Exporter);
- @EXPORT_OK = qw(time_age2cs time_age2riscos time_age2unix
- time_cs2age time_cs2riscos time_cs2unix
- time_riscos2age time_riscos2cs time_riscos2unix
- time_unix2age time_unix2cs time_unix2riscos
- time_guess2riscos $s_1900to1970 $seconds_per_day
- time2utc time2local time2_raw standard_date_and_time);
- $VERSION = 0.04;
-
- $time2utc = SWINumberFromString("XTerritory_ConvertTimeToUTCOrdinals");
- $time2local = SWINumberFromString("XTerritory_ConvertTimeToOrdinals");
- $time2_mask = ®mask([0..2]);
- $standard_date_and_time = SWINumberFromString("XOS_ConvertStandardDateAndTime");
- $standard_date_and_time_mask = ®mask([0..2]);
-
- # Make constants
- # Seconds between 1st January 1900 and 1st January 1970
- *s_1900to1970 = \2208988800; # 17 leap years, 53 normal
- *cs_1900to1970_256 = \862886250; # cs ÷ 256
- *seconds_per_day = \86400;
- *cs_per_day_256 = \33750;
- # Start time of script in cs/256 since 1st January 1900.
- *start = \($^T / 2.56 + $cs_1900to1970_256);
-
- #$time2utc && $time2local && $standard_date_and_time;
- #__DATA__
-
- sub time_age2cs {
- return ($^T - $_[0] * $seconds_per_day + $s_1900to1970) * 100
- unless wantarray;
- map { ($^T - $_ * $seconds_per_day + $s_1900to1970) * 100 } @_
- }
-
- sub time_age2riscos {
- my $temp; # Remember subroutines are pass by reference, so assigning to
- # $_[0] clobbers the caller's array.
- unless (wantarray) {
- $temp = $start - $_[0] * $cs_per_day_256;
- return pack 'CV', ($temp - int $temp) * 256, $temp;
- }
- map {
- $temp = $start - $_ * $cs_per_day_256;
- pack 'CV', ($temp - int $temp) * 256, $temp;
- } @_
- }
-
- sub time_age2unix {
- return pack 'I', ($^T - $_[0] * $seconds_per_day) unless wantarray;
- map { pack 'I', ($^T - $_ * $seconds_per_day) } @_
- }
-
- sub time_cs2age {
- return ($^T + $s_1900to1970 - $_[0] / 100) / $seconds_per_day
- unless wantarray;
- map { ($^T + $s_1900to1970 - $_ / 100) / $seconds_per_day } @_
- }
-
- sub time_cs2riscos {
- # Inspection of pp.c reveals that % (ie pp_modulo) doesn't do doubles. :-(
- my $temp;
- unless (wantarray) {
- $temp = int $_[0] / 256;
- return pack 'CV', $_[0] - $temp * 256, $temp;
- }
- map {
- $temp = int $_ / 256;
- pack 'CV', $_ - $temp * 256, $temp;
- } @_
- }
-
- sub time_cs2unix {
- return pack 'I', ($_[0] / 100 - $s_1900to1970) unless wantarray;
- map { pack 'I', ($_ / 100 - $s_1900to1970) } @_
- }
-
- sub time_riscos2age {
- # ord equivalent to unpack ('C'). Hence whole is unpack 'CV'
- return ($start - ord ($_[0]) / 256 - unpack ('xV', $_[0])) / $cs_per_day_256 unless wantarray;
- map { ($start - ord ($_) / 256 - unpack ('xV', $_)) / $cs_per_day_256 } @_
- }
-
- sub time_riscos2cs {
- return ord ($_[0]) + 256 * unpack ('xV', $_[0]) unless wantarray;
- map { ord ($_) + 256 * unpack ('xV', $_)} @_
- }
-
- sub time_riscos2unix {
- # Very odd conversion to want to make
- time_cs2unix (&time_riscos2cs)
- }
-
- sub time_unix2age {
- return ($^T - unpack 'I', $_[0]) / $seconds_per_day unless wantarray;
- map { ($^T - unpack 'I', $_ ) / $seconds_per_day } @_
- }
-
- sub time_unix2cs {
- return 100 * ($s_1900to1970 + unpack 'I', $_[0]) unless wantarray;
- map { 100 * ($s_1900to1970 + unpack 'I', $_[0]) } @_
- }
-
- sub time_unix2riscos {
- # Less odd conversion to want to make
- time_cs2riscos (&time_unix2cs)
- }
-
- sub _time_guess2riscos ($) {
- return undef unless defined $_[0];
- my $length = length $_[0];
-
- # String is an "age" if all the characters are numeric.
- # count the non-numeric. Can't use tr on read only values. :-(
- # OK. if we can match 1 non-numeric.
- return &time_age2riscos unless ($_[0] =~ m/[^-\d.]/);
- return $_[0] if ($length == 5);
- return &time_unix2riscos if ($length == 4);
- if ($length == 6 and $_[0] =~ /\0$/) {
- # Sanity check that last character is "\0"
- my $time = $_[0];
- chop ($time);
- return $time;
- }
- undef;
- }
-
- sub time_guess2riscos {
- return _time_guess2riscos ($_[0]) unless wantarray;
- map { _time_guess2riscos $_ } @_
- }
-
- sub standard_date_and_time ($) {
- my ($time) = $_[0]; # Don't shift
- unless (defined $time and length ($time) == 5) {
- $time = &_time_guess2riscos; # Pass on @_
- }
-
- my $buffer = 'x'x256;
- return undef
- unless defined $time and swix ($standard_date_and_time,
- $standard_date_and_time_mask,
- $time, $buffer, 256);
- $buffer =~ /^([^\0]*)/; # Everything until the first \0
- $1;
- }
- sub time2_raw ($$;$) {
- my ($swi, $time, $terr) = @_;
- $terr = -1 unless defined $terr; # Won't hurt for UTC call
- unless (defined $time and length ($time) == 5) {
- $time = &_time_guess2riscos ($time);
- }
-
- my $buffer = 'x'x36;
- return ()
- unless defined $time
- and defined swix ($swi, $time2_mask, $terr, $time, $buffer);
- unpack 'I*', $buffer;
- }
-
- sub fix_os {
- return wantarray ? () : undef
- unless my @raw = &time2_raw; # Pass on @_
- shift @raw; # Loose centiseconds
- $raw[4]--; # Lots of bloody fiddly corrections from OS to ANSI
- $raw[5] -= 1900;
- $raw[6]--;
- $raw[7]--;
- @raw;
- }
-
- sub time2utc ($) {
- return () unless defined $_[0];
- return (gmtime (unpack 'I', $_[0]))[0..7] if 4 == length $_[0];
- (fix_os $time2utc, @_); # Always list context
- }
-
- sub time2local ($;$) {
- croak "Can't yet convert territory $_[1]"
- if defined $_[1] and -1 != $_[1];
- return &standard_date_and_time unless wantarray;
- return (localtime (unpack 'I', $_[0]))[0..7] if 4 == length $_[0];
- fix_os $time2local, @_;
- }
-
- $time2utc && $time2local && $standard_date_and_time;
- __END__
-
- =head1 NAME
-
- RISCOS::Time -- perl interface to S<RISC OS> time SWIs
-
- =head1 SYNOPSIS
-
- use RISCOS::Time qw (time2utc time2local);
- print 'symtable updated ', scalar time2local $time;
- print 'script started ' . time2utc (0); # Auto converts from "age"
-
- =head1 DESCRIPTION
-
- This module provides perl interface to the SWIs
- C<OS_ConvertStandardDateAndTime>, C<Territory_ConvertTimeToUTCOrdinals> and
- C<Territory_ConvertTimeToOrdinals> and functions to convert between different
- formats for storing time information.
-
- Time formats currently understood are
-
- =over 4
-
- =item * numeric ages as returned by perl's C<-A>, C<-C> and C<-M> functions
-
- =item 4 byte scalars - Unix times, seconds starting from 1st January 1970
-
- =item 5 byte scalars - S<RISC OS> times, centiseconds starting from 1st January 1900
-
- =item 6 byte scalars - S<RISC OS> times, as stored in C<ALF> files.
-
- =back
-
- Clearly it is not possible to automatically distinguish between all these
- formats so where guessing is necessary the following heuristics are used:
-
- =over 4
-
- =item *
-
- If I<time> is composed soley of numeric characters (0-9, "-" and ".") then it
- is assumed to be an age in days since the time the script started running
- (see L<perlvar/$^T>). Clearly there is possible ambiguity with RISC OS 5 byte
- ages that happen to be eqactly 5 characters and composed of legal numeric
- characters. However the most recent 5 byte time which could be confused is
- C<"99999">, 23:17:53.85 on Friday 18th November 1977, which is likely to predate
- most stamped files by about a decade.
-
- =item *
-
- Otherwise C<if (length $time == 5)> then C<$time> is assumed to be a 5 byte RISC
- OS time, encoded as centiseconds since 1900/1/1.
-
- =item *
-
- C<if (length $time == 4)> then C<$time> is taken to be a 4 byte Unix time,
- seconds since 1970/1/1 C<pack>ed as C<'I'>.
-
- =item *
-
- C<if (length $time == 6)> then C<$time> is expected to be a 6 byte time as
- stored in C<ALF> files, also centiseconds since 1900/1/1. If the MSB is not 0
- the time is rejected, otherwise it is treated as the equivalent 5 byte RISC OS
- time.
-
- =back
-
- =head2 SWI interface
-
- As the SWI interface functions are expect to be used with native times if
- C<length $time == 5> then it is taken as a RISC OS time, taking priority over
- the guessing heuristic.
-
- =over 4
-
- =item standard_date_and_time <time>
-
- calls C<OS_ConvertStandardDateAndTime> for I<time>, returning the string
- representation of that time for the local timezone (the format being set by
- C<<Sys$DateFormatE<gt>>).
-
- =item time2utc <time>
-
- calls C<Territory_ConvertTimeToUTCOrdinals> for I<time>, returning an array of
- integers, adjusted to be consistent with the return array from C<gmtime>.
-
- # 0 1 2 3 4 5 6 7
- ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = time2utc($time);
-
- In particular this means that C<$mon> has the range 0..11 and C<$wday> has
- the range 0..6 with Sunday as day 0. B<Note> C<$year> is the number of
- years since 1900, B<not> simply the last two digits of the year.
- If C<length $time == 4> calls C<gmtime> directly, but drops the 9th element
- ("is daylight saving time").
-
- =item time2local <time> [, <territory>]
-
- calls C<Territory_ConvertTimeToOrdinals> for I<time> to convert to local times.
- In scalar context behaves as a call to C<standard_date_and_time>, in array
- context returns an array as described in C<time2utc>. If C<length $time == 4>
- calls C<localtime> directly. I<territory> defaults to C<-1> - the current
- territory.
-
- Support for territories other than C<-1> (the current territory) is rather
- limited - currently territories may only be used in array context and must be
- specified by number only, rather than name. Calls in scalar context may only use
- the current territory. (Otherwise the script C<die>s with an error).
-
- =item time2_raw <swi_number>, <time> [, <territory>]
-
- calls the supplied SWI (which is expected to be
- C<Territory_ConvertTimeToUTCOrdinals> or C<Territory_ConvertTimeToOrdinals>) and
- returns an array of integers
-
- # 0 1 2 3 4 5 6 7 8
- ($csec,$sec,$min,$hour,$mday,$mon,$year,$wday,$yday)
-
- where the ranges for C<$mon>, C<$wday> and C<$yday> run from B<1> upwards
- (I<c.f.> B<0> upwards for the other subroutines and the perl builtins) and
- C<$year> is the full Gregorian year (rather then the number of years since
- 1900).
-
- =back
-
- Values are returned consistent with ANSI C and perl's idea of the base values
- for days of the week, days of the year I<etc.> from C<time2utc> and
- C<time2local> - the native RISC OS values are available from C<time2_raw>.
-
- =head2 Conversion functions
-
- 13 functions are provided to convert between different time formats. In scalar
- context all convert only the first argument, in list context a list of
- conversions corresponding to the argument list.
-
- =over 4
-
- =item time_guess2riscos
-
- Each argument is processed according to the guessing heuristics to determine
- which conversion to use.
-
- =item time_age2cs
-
- =item time_age2riscos
-
- =item time_age2unix
-
- =item time_cs2age
-
- =item time_cs2riscos
-
- =item time_cs2unix
-
- =item time_riscos2age
-
- =item time_riscos2cs
-
- =item time_riscos2unix
-
- =item time_unix2age
-
- =item time_unix2cs
-
- =item time_unix2riscos
-
- Arguments are converted from the first named format to the second, where formats
- are
-
- =over 4
-
- =item age
-
- numeric ages as returned by perl's C<-A>, C<-C> and C<-M> functions (see
- L<perlfunc/-X>)in days since the script start time (C<$^T>).
-
- =item cs
-
- centiseconds since 1900/1/1 expressed as a number.
-
- =item riscos
-
- centiseconds since 1900/1/1 packed as 5 bytes.
-
- =item unix
-
- seconds since 1970/1/1 packed as 4 bytes.
-
- =back
-
- =back
-
- =head2 Constants
-
- C<RISCOS::Time> is able to export these "useful" constants:
-
- =over 4
-
- =item $s_1900to1970
-
- C<2208988800>, the number of seconds between Janaury 1st 1900 and 1970.
-
- =item $seconds_per_day
-
- C<86400>, the number of seconds in a day.
-
- =back
-
- =head1 BUGS
-
- Support for territories other than the current territory is very limited. It
- might help if Acorn supplied more than one territory on the standard UK machine.
-
- =head1 AUTHOR
-
- Nicholas Clark <F<nick@unfortu.net>>
-
- =cut
-