#!perl $rdir=shift || &printargs; $map=shift || &printargs; $mod=shift || &printargs; $startdate=shift || &printargs; $enddate=shift || &printargs; $dateinc=shift || &printargs; die "bad date format $startdate" unless $startdate=~s@^(\d\d\d\d)/(\d\d)/(\d\d)$@\1\2\3@; die "bad date format $enddate" unless $enddate=~s@^(\d\d\d\d)/(\d\d)/(\d\d)$@\1\2\3@; $jday=MJD($startdate); $jday1=MJD($enddate); for($day=$jday;$day<=$jday1;$day+=$dateinc) { ($y, $m, $d)=DJM($day); $p4cmd="p4 sync $rdir\\...\@$y/$m/$d:01:00:00 >nul 2>&1"; $hl2cmd="$rdir\\hl2 -game $mod -sw +map $map -makedevshots -dev -width 1024 -height 768"; print "Taking shots for $m/$d/$y\n"; print "$p4cmd\n"; print `$p4cmd`; print "hl2cmd\n"; print `$hl2cmd`; } sub printargs { print STDERR "format is SHOTMAKER.PL rootdir mapname mod startdate enddate dateincrement\n"; print STDERR "ex:\nSHOTMAKER u:\\dev\\valvegames\\main\\game ep1_c17_01 episodic 2005/10/01 2005/10/05 7\n"; die; } # Toby Thurston --- 12 May 2003 use strict; use Carp; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS @mon @dom); require Exporter; @ISA = qw(Exporter); $VERSION = '0.03'; =head1 NAME Cal::Date - a simple set of calendar functions for Perl (yes, yes, I know about L and L but mine is simpler, and nicer :-). =head1 SYNOPSIS use Cal::Date qw(DJM MJD today); $date = $ARGV[0] || today(); print "$date --> " . MJD($date) . "\n"; print "Day after -->" . DJM(MJD($date)+1) . "\n"; =head1 DESCRIPTION A simple compact interface to some simple calendar routines. Implemented purely in Perl, no need for external C code etc. =head1 FUNCTIONS No functions are exported by default. =cut @EXPORT = qw(); =pod The following functions can be exported from the C module: MJD DJM Easter old_style_Easter orthodox_Easter ISO_week ISO_day ISO_week_and_day day_of_year days_to_go days_in_month UK_tax_week UK_tax_month working_days today now J2G v_date r_date adjust_to_local_time adjust_to_UTC is_a_date =cut @EXPORT_OK = qw( MJD DJM Easter old_style_Easter orthodox_Easter ISO_week ISO_day ISO_week_and_day day_of_year days_to_go days_in_month UK_tax_week UK_tax_month working_days today now J2G v_date r_date adjust_to_local_time adjust_to_UTC is_a_date ); =pod You can import all of them at once with C =cut %EXPORT_TAGS = (all => [@EXPORT_OK]); =over 4 =item MJD(yyyymmdd) or MJD(y,m,d) MJD returns the `modified julian day' number for a date. This is suitably small integer that you can use as the basis of many date calculations. You can call C with a single 8 digit string representing a date in compact ISO form, C, or with three integers representing year, month and day of the month. Unlike the values returned from the C etc. functions, year is the full AD year and month 1 is January. Other than checking that the arguments are whole numbers, the internal function C<_getYMD> does no range checking. This is a feature rather than a bug. It means you can use 0 as a month number to refer to December in the previous year, and 13 to refer to January in the next year. For example, assuming C<$month == 12>, the following are equivalent: MJD(19991301); MJD(1999, $month+1, 1); MJD(20000101); MJD(2000, 1, 1); You can do the same trick with the day numbers too; this provides a handy way to refer to the last day of the previous month. Thus C refers to 31 December 1999 (but note that C refers to 30 November 1999). This works with leap years too (of course) so C refers to the last day of February for any value of C<$y>. =cut sub MJD { # returns mjd from yyyymmdd or y,m,d use integer; my ($y, $m, $d) = &_getYMD; # allow month to be enormous while ( $m > 12) { $m -= 12; $y++ } # adjust the month/year to make it a date after 1 March if ($m < 3) { $m += 12; $y-- } # work out days upto and including the day before the previous 1 March # year * 365 + leap days - 306 # we are using the (possibly proleptic) Gregorian calendar my $mjd = $y*365 + $y/4 - $y/100 + $y/400 - 306; # add days since previous 1 March (incl) $mjd += ($m+1)*306/10 - 122 + $d; # adjust so 0 == 18 Nov 1858 == JD 2,400,000.5 $mjd -= 678576; return $mjd; } =item DJM(mjd) This function is the inverse of the C function, hence the rather cute name. It takes any number, interprets it as an MJD number and returns the corresponding date in the ISO compact form of YYYYMMDD. This form has the advantage of being easily sorted and compared. C is often used in combination with MJD. For example to `correct' a date use C. If your input date was 20000300, this will return 20000229. This idiom can also be used to check that an input date is valid. Like this: if ($date ne DJM(MJD($date)) ) { print "$date is not a valid YYYYMMDD date\n"; } When you pass a real number to C the fractional part is interpreted as a fraction of a day, and the date and time are returned in C form. Like this: print DJM(51455.7356) . "\n"; # prints 19991004 17:39 If you call C in a list context then the parts of the date/time are returned as elements of a list, like this: ($y, $m, $d, $hr, $min) = DJM(51455.7356); ($y, $m, $d) = DJM(51500); =cut sub DJM { # returns yyyymmdd from mjd return unless defined wantarray; # don't bother doing more # the supplied MJD may be integer (hour=midnight) or real # the fractional part repesents the time of day my $mjd = shift; # convert to full Julian number my $jd = $mjd + 2400000.5; # jd0 is the Julian number for noon on the day in question # for example mjd jd jd0 === mjd0 # 3.0 ...3.5 ...4.0 === 3.5 # 3.3 ...3.8 ...4.0 === 3.5 # 3.7 ...4.2 ...4.0 === 3.5 # 3.9 ...4.4 ...4.0 === 3.5 # 4.0 ...4.5 ...5.0 === 4.5 my $jd0 = int($jd+0.5); # next we convert to Julian dates to make the rest of the maths easier. # JD1867217 = 1 Mar 400, so $b is the number of complete Gregorian # centuries since then. The constant 36524.25 is the number of days # in a Gregorian century. The 0.25 on the other constant ensures that # $b correctly rounds down on the last day of the 400 year cycle. # For example $b == 15.9999... on 2000 Feb 29 not 16.00000. my $b = int(($jd0-1867216.25)/36524.25); # b-int(b/4) is the number of Julian leap days that are not counted in # the Gregorian calendar, and 1402 is the number of days from 1 Jan 4713BC # back to 1 Mar 4716BC. $c represents the date in the Julian calendar # corrected back to the start of a leap year cycle. my $c = $jd0+($b-int($b/4))+1402; # d is the whole number of Julian years from 1 Mar 4716BC to the date # we are trying to find. my $d = int(($c+0.9)/365.25); # e is the number of days from 1 Mar 4716BC to 1 Mar this year # using the Julian calendar my $e = 365*$d+int($d/4); # c-e is now the remaining days in this year from 1 Mar to our date # and we need to work out the magic number f such that f-1 == month my $f = int(($c-$e+123)/30.6001); # int(f*30.6001) is the day of the start of the month # so the day of the month is the difference between that and c-e+123 my $day = $c-$e+123-int(30.6001*$f); # month is now f-1, except that Jan and Feb are f-13 # ie f 4 5 6 7 8 9 10 11 12 13 14 15 # m 3 4 5 6 7 8 9 10 11 12 1 2 my $month = ($f-2)%12+1; # year is d - 4716 (adjusted for Jan and Feb again) my $year = $d - 4716 + ($month<3); # finally work out the hour (if any) my $hour = 24 * ($jd+0.5-$jd0); if ( $hour == 0) { if (wantarray) { return ($year, $month, $day) } else { return sprintf "%d%02d%02d", ($year, $month, $day) } } else { $hour = int($hour*60+0.5)/60; # round to nearest minute my $min = int(0.5+60 * ($hour - int($hour))); $hour = int($hour); if (wantarray) { return $year, $month, $day, $hour, $min } else { return sprintf "%d%02d%02d %02d:%02d", $year, $month, $day, $hour, $min } } } =item today() or today(delta) This function returns today's date in YYYYMMDD form, saving you all that tedious mucking about with lists and Cs. It uses C so you get the date adjusted for local time zone, depending on the time of day this may or may not be the same as the date at Greenwich. Use C to get the UTC date if that's what you want. You can supply a number of days as an optional parameter. This number (which may be negative) will be added to the current date. The number should be a either a whole number of days or a week specification in a form that will match C. For example: C<1w> means one week, C<-2w3> means -17 days. =cut sub today { # return YYYYMMDD for today return unless defined wantarray; my $delta = &_get_delta; return DJM(MJD()+$delta); } sub _get_delta { my $delta = shift || 0; if ($delta =~ /^([+-])?(\d+)[wW](\d)?$/) { local $^W=0; # disable warnings for unitialized $1 or $3 $delta = $1.($2*7+$3) } if ( $delta !~ /^([+-]?\d+)$/ ) { croak "Bad value for day shift: $delta\n"; } return $delta; } sub now { # return hh:mm for now return unless defined wantarray; my ($s, $m, $h) = localtime(); return wantarray ? ($h,$m,$s) : sprintf("%02d:%02d:%02d", $h, $m, $s); } =item Easter(year,[delta]) This function takes a year number and returns the date of Easter Sunday in YYYYMMDD form for that year. See below about valid years. The date is supposed to be the first Sunday after the calendar full moon which occurs on or after 21 March. The name Easter comes from the Saxon goddess of the dawn, Eostre, whose festival was celebrated at the vernal equinox. You can supply a number of days as an optional parameter. This number (which may be negative) will be added to the resulting date. This is handy for working out dates that depend on Easter. For example: $y = 2000; $s = Easter($y,-47); # Shrove Tuesday (Pancake Day) $m = Easter($y,-21); # Mothers day in the UK $a = Easter($y,+39); # Ascension day The format of the number should be as described above under L. The algorithm used was adapted from D. E. Knuth I, as Knuth notes it is derived from older sources, and is only valid after 1582 when the Gregorian calendar was first used in Europe (but not in Britain). For years before this use the L routine below, which returns Julian dates such as were in use then. I have only validated this routine back to 1066, the earliest I could find a list in my reference books at home, but it should be valid further back. I do not know when Easter was first celebrated as Easter. =cut sub Easter { return unless defined wantarray; # don't bother doing more use integer; my $y = shift; my $delta = &_get_delta; my $golden = $y%19 + 1; my $century = $y/100 + 1; my $x = 3*$century/4 - 12; my $q = 5*$y/4 - $x - 10; my $epact = (11*$golden + 15 + (8*$century + 5)/25 - $x) % 30; ++$epact if ($epact == 25 && $golden > 11) || $epact == 24; my $d = 44 - $epact; $d += 30 if $d < 21; $d = $d + 7 - (($q+$d)%7); return DJM(MJD($y,3,$d)+$delta); } =item old_style_Easter(year,[delta]) This function is mainly of historical interest. Before the switch to Gregorian dates that happened in 1582 in certain parts of Roman Catholic Europe, the Julian calendar was used. This routine gives you the date of Easter in the Julian calendar. Because of the way Easter is derived, this is not a constant number of days apart from the date in Gregorian. Typically it can be either 4 or 5 weeks or just a few days. In British historical records between 1582 and 1752 (when Britain switched) the Julian dates are referred to as `old style' and the Gregorian dates as `new style'. Hence my name for this function. This algorithm is based on details found on the web which referred to the algorithm of Oudin (1940), quoted in I, P. Kenneth Seidelmann, editor. You can add an optional day shift number as above in L. =cut sub old_style_Easter { return unless defined wantarray; # don't bother doing more use integer; my $y = shift; my $delta = &_get_delta; my $g = $y % 19; my $i = (19*$g + 15) % 30; my $j = ($y + $y/4 + $i) % 7; my $l = $i - $j; my $m = 3 + ($l + 40)/44; my $d = $l + 28 - 31*($m/4); return DJM(MJD($y,$m,$d)+$delta); } =item orthodox_Easter(year,[delta]) The various Orthodox parts of the Christian church (principally in Greece, the Balkans and other parts of eastern Europe and Russia) still use the Julian calendar (the `old style') to work out the date of Easter, but they express the result in new style, Gregorian dates. This routine may be handy if you belong to such a church or if you are planning a spring holiday in Greece, where Easter is always a special time. This is essentially just old_style_Easter corrected to Gregorian dates with the L function. =cut sub orthodox_Easter { my ($y,$m,$d) = &old_style_Easter; return DJM(MJD($y,$m,$d)+J2G($y,$m,$d)); } =item ISO_week(yyyymmdd) or ISO_week(y,m,d) This function returns the week number according to the ISO standard. This states that weeks begin on a Monday (day 1), and that the first week of a year is the one with 4 Jan in it. The function returns the date in the ISO week form: yyyy-Wnn. The year is included as it may differ from the year of the date in yyyymmdd form. For example C returns C<1999-W52>. The ISO day number for a given date is given by C. See below. =cut sub ISO_week { return unless defined wantarray; # don't bother doing more use integer; my ($y, $m, $d) = &_getYMD; my $jan1 = MJD($y,1,1); my $week = (MJD($y,$m,$d) - $jan1 + 1 + ($jan1+5)%7 + 3) / 7; if ( $week == 0 ) { # week belongs to last year $y--; # work out if its W52 or W53 $jan1 = MJD($y,1,1); $week = (MJD($y,12,31) - $jan1 + 1 + ($jan1+5)%7 + 3) / 7; } elsif ( $week == 53 ) { # week might belong to next year # if 31 Dec is Weds or earlier if (ISO_day(MJD($y,12,31)) < 4) { $y++; $week = 1; } } return wantarray ? ($y, $week) : sprintf "%d-W%02d", $y, $week; } =item ISO_day(mjd) This function returns the ISO day number for a given MJD value. According to ISO, Monday is day 1 and Sunday day 7 in the week. To find today's ISO day number do: print ISO_day(MJD(today())); I occasionally find that I call this with a date by mistake for an MJD number, so as a convenience if the MJD number is over 10,000,000 we will interpret it as a date. This means that ISO_day won't work for dates after 29237-12-12, which we can probably live with, but that c gives a less astonishing result. =cut sub ISO_day { my $mjd = shift; if ($mjd > 10_000_000) { $mjd = MJD($mjd); } if ($mjd > -3) { return ($mjd+2)%7+1; } else { return abs(9+$mjd%7)%7+1; } } =item ISO_week_and_day(yyyymmdd) or ISO_week_and_day(y,m,d) Converts a given date to ISO Week.Day form, sometimes known as business date form. For example 19991215 maps to 1999-W51-6 =cut sub ISO_week_and_day { return unless defined wantarray; # don't bother doing more return wantarray ? (&ISO_week, ISO_day(&MJD)) : &ISO_week . '-' . ISO_day(&MJD) } =item day_of_year(yyyymmdd) or day_of_year(y,m,d) This function returns the day number of the current year, where Jan 1 = 1, Feb 1 = 32 etc. It is implemented simply as MJD($y,$m,$d) - MJD($y-1,12,31) =cut sub day_of_year { my ($y, $m, $d) = &_getYMD; return MJD($y,$m,$d)-MJD($y,1,0); } =item days_to_go(yyyymmdd) or days_to_go(y,m,d) This function returns the days to the end of the year, where Dec 31 = 0, Dec 30 = 1, etc. Again it is simply implemented as MJD($y,12,31)-MJD($y,$m,$d); =cut sub days_to_go { my ($y, $m, $d) = &_getYMD; return MJD($y,12,31)-MJD($y,$m,$d); } =item days_in_month(y,m) This function returns the days in the current month. It is implemented like this: MJD($y,$m+1,1)-MJD($y,$m,1); Note that this works even in December (when C<$m==12>) because C interprets 13 to mean January next year. You may find it easier to use MJD directly for this function, and save an import. =cut sub days_in_month { my ($y, $m) = @_; return MJD($y,$m+1,1)-MJD($y,$m,1); } =item UK_tax_week(yyyymmdd) or UK_tax_week(y,m,d) This function is specific to UK Income Tax or `Pay As You Earn' rules. It returns a string indicating the week in the tax year corresponding to a given date. The UK tax year starts on April 5 each year. Example: print UK_tax_week(19991225); # Prints: PAYE Week 38 =cut sub UK_tax_week { my ($y, $m, $d) = &_getYMD; my $april6 = MJD($y,4,6); my $today = MJD($y,$m,$d); if ($april6 > $today ) { $april6 = MJD($y-1,4,6) } use integer; return sprintf "%d", ($today-$april6)/7+1; } =item UK_tax_month() This function is also specific to UK Income Tax or `Pay As You Earn' rules. It returns a string indicating the month in the tax year corresponding to a given date. The UK tax year starts on April 5 each year. Example: print UK_tax_month(19991225); # Prints: PAYE Month 9 =cut sub UK_tax_month { my ($y, $m, $d) = &_getYMD; return sprintf "%d", ($m+8-($d<6))%12+1; } =item working_days(y,m,d,period) or working_days(y,m,d,y2,m2,d2) This function returns the number of working days in a given period including start day. Call it with a date and a number of days or with two dates. The number of days returned is simply the number of non-weekend days, no account is taken of holidays etc. More sophisticated functions can be found in the C package. The two dates can be given in either order. Should they be the same, then 1 or 0 may be returned depending on whether the day in question was a working day or not. =cut sub working_days { my ($start,$end,$m,$count); $start = MJD($_[0],$_[1],$_[2]); if (@_ == 4) { $end = $start + $_[3] - 1; } elsif (@_ == 6) { $end = MJD($_[3],$_[4],$_[5]); } else { croak "Bad call to working days: $!\n" } if ($start > $end ) { ($start,$end) = ($end,$start)} if ($end-$start > 10000 ) { return 'Lots' } $count = 0; for $m ($start..$end) { ++$count if ISO_day($m) < 6 } return $count; } =item v_date(year,datespec[,delta]) v_date returns a date as a real MJD (or (y,m,d,h,min,s) in list context) optionally shifted by delta days, based on the specification in datespec and the given year. The format of the delta number should be as described above under L. This specification can be one of the standard variable date forms used in setting a Posix TZ environment variable, extended as noted here. The main form is Mmm.w.d where `mm' is the month (1-12) number, `w' is the week of the month (1-5 or L) note that 5 and L are equivalent and refer to the last week of the months (either the fourth or fifth depending on the length of the month), and `d' is the day of the week (0-7) where 1 = Monday and 7 (or 0) = Sunday. The use of L and 7 above are extensions to the Posix rules. Further you can extend the meaning of `d' to allow you to specify for example the last working day in a month. You do this by adding to the d number, eg: M10.L.12345 means the last working day of October, while M1.1.67 means the first weekend day in January. Other forms are... - Jddd which refers to the day of the year, regardless of leap days (ie 1 March is always day J60 etc). - ddd which refers to the day of the year counting leap days, (ie day 60 is Feb 29 in leap years or Mar 1 in non-leap years. - Dmm.d.w which is exactly the same as the M form, but with the w and d fields reversed. Any of the specs may be followed by "/hh[:mm[:ss]]" to indicate a particular time. v_date returns undef if called with an invalid spec. =cut sub v_date { return unless defined wantarray; my $y = shift; my $spec = shift; my $delta = &_get_delta; my ($m,$w,$d,$mjd,$time,$dshift); # remove any time from spec if ( $spec =~ /(.*)\/(\d+)(:(\d+)(:(\d+))?)?/ ) { $time = $2; if ( defined($4) ) { $time += $4/60; if ( defined($6) ) { $time += $6/3600; } } $spec = $1; } else { $time = 0 } # change D.... to M.... if (($m,$d,$w) = $spec =~ /^D([0-1]?\d).([0-7]+).([1-5L])$/ ) { $spec = "M$m.$w.$d"; } # Mmm.w.d if (($m,$w,$d) = $spec =~ /^M([0-1]?\d).([1-5L]).([0-7]+)$/ ) { if ($w =~ /[1-4]/ ) { $mjd = MJD($y,$m,1) + 7*($w-1); $dshift = 7; for my $n ( split(/ */,$d)) { $n = $n - ISO_day($mjd); if ($n<0) { $n += 7 } if ($n<$dshift) { $dshift = $n } } } else { # 5 or L $mjd = MJD($y,$m+1,0); $dshift = 7; for my $n ( split(/ */,$d) ) { $n = $n - ISO_day($mjd)%7; if ($n>0) { $n -= 7 } if (abs($n)59) { $mjd = MJD($y,3,1)+$d-60+$delta } else { $mjd = MJD($y,1,0)+$d+$delta } } # nnn ... elsif (($d) = $spec =~ /^(\d+)$/ ) { $mjd = MJD($y,1,0)+$d+$delta } else { croak "Malformed spec for v_date: $spec\n"; } $mjd += $time/24; return wantarray ? DJM($mjd) : $mjd; } =item r_date(dow[,every[,start[,end]]]) This routine generates a list of MJD integers corresponding to a set of repeating dates defined by the argument list. The set may be empty in which case an empty list is returned. In the scalar context you get the number of dates in the list. The list is returned sorted in ascending numerical order. dow: should match C, that is at least one and at most seven digits between 1 and 7 with no repetitions. So "1" means Mondays, "6" means Saturdays, "14" means Mondays and Thursdays and so on. every: 1 means every dow, 2 means every other dow, 3 means every third dow, etc. Every defaults to 1. start: is a date in yyyymmdd form. The first date in the returned list will be on or after this date. Start defaults to Jan 1st in the current year. end: is another date in yyyymmdd form. The last date in the returned list will be on or before this date. End defaults to Dec 31st in the current year. Some examples: r_date(1) returns a list of every Monday in the current year r_date(2,2,20030101,20030700) returns every other Tuesday in the first half of 2003 r_date(15,1,20030501,20030531) every Monday and Friday in June 2003 =cut sub r_date { return unless defined wantarray; my (undef,undef,undef,undef,undef,$y) = localtime; my $days = shift; my $every = shift; my $start = shift; my $end = shift; return undef unless defined $days && $days =~ /\d+/ && $days =~ /^1?2?3?4?5?6?7?$/; $every = 1 unless defined $every && $every =~ /^\d+$/ && $every<100; if ( defined $start && $start=~/^\d{8}$/ ) { $start = MJD($start) } else { $start = MJD($y,1,1) } if ( defined $end && $end =~/^\d{8}$/ ) { $end = MJD($end) } else { $end = MJD($y,12,31) } my @list = (); for my $dow ( split / */, $days) { my $day_shift = $dow - ISO_day($start); $day_shift += 7 if $day_shift < 0; my $first_date = $start + $day_shift; for (my $i=0; $first_date+$i<$end; $i+=7*$every) { push @list, $first_date+$i; } } return sort @list; } =item adjust_to_local_time(mjd,tzoffset,tzrule1,tzrule2[,DST_delta]) This routine takes a real MJD number --- representing a UTC date and time --- and adjusts it for time zone making proper allowance for summer time or `daylight saving time' (DST). The second argument is the normal difference between UTC and local time (ie New York = +5) in hours. The third and fourth arguments are two rules that define when DST should start when it should stop. If the rules are empty or undefined then the routine returns the MJD adjusted to local time with no allowance for summer time. The rules are rules in the format understood by C. The fifth argument represents the number of hours that the clocks go forward when DST starts. If this is omitted it will default to 1. This default was not always correct historically but as far as I have been able to verify it is currently, so you can nearly always omit the fifth argument. =cut sub adjust_to_local_time { my $mjd = shift; my $tz = shift || $Cal::Astro::tz; my $r1 = shift || $Cal::Astro::r1; my $r2 = shift || $Cal::Astro::r2; my $dst_delta = shift || 1; # stop here if no date given return '' unless defined($mjd); return '' if $mjd eq ''; # stop here if no TZ given return $mjd unless defined($tz); # adjust for time zone $mjd = $mjd-$tz/24; # stop here if no summer time rules return $mjd unless defined($r1) && defined($r2); # make rules into dates for the current year my ($year) = DJM($mjd); my $d1 = v_date($year,$r1); my $d2 = v_date($year,$r2); # are we in DST at the start of the year? # (ie does r1 say October rather than March/April) my $jan_state = ($d1 > $d2); # swap the dates so that d1 < d2 ($d1,$d2) = ($d2,$d1) if $jan_state; # if the date is in the summer set the opposite of # the state at the start of the year & adjust if needed if ($d1 <= $mjd && $mjd < $d2 ) { return $mjd + $dst_delta/24 * !$jan_state; } # otherwise return the state at the start of the year return $mjd + $dst_delta/24 * $jan_state; } =item adjust_to_UTC(mjd,tzoffset,tzrule1,tzrule2[,DST_delta]) This routine takes a real MJD number --- representing a local date and time --- and adjusts it back to UTC allowing for local time zone and summer time rules. The arguments are all exactly the same as those for C. =cut sub adjust_to_UTC { my $mjd = shift; my $tz = shift || $Cal::Astro::tz; my $r1 = shift || $Cal::Astro::r1; my $r2 = shift || $Cal::Astro::r2; my $dst_delta = shift || 1; return adjust_to_local_time($mjd,-$tz,$r1,$r2,-$dst_delta); } sub is_a_date { my $date = shift; $date =~ s/[^0-9]//g; return 0 unless $date =~ /\d{8}/; return $date eq DJM(MJD($date)); } sub _getYMD { my ($y, $m, $d); if ( @_ == 0 ) { (undef, undef, undef, $d, $m, $y) = localtime(); $y += 1900; $m ++; } elsif ( @_ == 1 && !defined $_[0] ) { my ($package, $filename, $line) = caller; croak "\nCal::Date routine called with undefined value by $package \nLook at $filename, line $line\n"; } elsif ( @_ == 1 && $_[0] =~ /^\d+$/ && $_[0] > 100000 ) { $y = substr($_[0],0,-4); $m = substr($_[0],-4,2); $d = substr($_[0],-2); } elsif ( @_ == 1 && $_[0] =~ /^\d+$/ ) { # probably an MJD as it is so small ($y, $m, $d) = DJM($_[0]); } elsif (@_ == 1 && $_[0] =~ /^(\d\d\d\d)-(\d\d)-(\d\d)$/ ) { ($y, $m, $d) = ($1, $2, $3); } elsif ( @_ == 3 && $_[0] =~ /^\d+$/ && $_[1] =~ /^[-]?\d+$/ && $_[2] =~ /^[-]?\d+$/) { ($y, $m, $d) = @_ } else { croak "Can't read a date from this --> [@_]" } return ($y, $m, $d); } sub J2G { # returns days difference between julian on gregorian dates use integer; my ($y, $m, $d) = &_getYMD; # if the month is Jan or Feb then use the year before if ($m < 3) { $y-- } # the difference in leap days is just the omitted century end leap days in the # Gregorian calendar, less two because they didn't start until # some long time after 1 AD return $y/100 - $y/400 - 2; } =back =head1 SEE ALSO L and L packages which provide more comprehensive functions; as they say: there's more than one way to do it. =head1 AUTHOR Toby Thurston web: http://www.wildfire.dircon.co.uk =cut 1;