Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

955 lines
29 KiB

  1. #!perl
  2. $rdir=shift || &printargs;
  3. $map=shift || &printargs;
  4. $mod=shift || &printargs;
  5. $startdate=shift || &printargs;
  6. $enddate=shift || &printargs;
  7. $dateinc=shift || &printargs;
  8. die "bad date format $startdate" unless $startdate=~s@^(\d\d\d\d)/(\d\d)/(\d\d)$@\1\2\3@;
  9. die "bad date format $enddate" unless $enddate=~s@^(\d\d\d\d)/(\d\d)/(\d\d)$@\1\2\3@;
  10. $jday=MJD($startdate);
  11. $jday1=MJD($enddate);
  12. for($day=$jday;$day<=$jday1;$day+=$dateinc)
  13. {
  14. ($y, $m, $d)=DJM($day);
  15. $p4cmd="p4 sync $rdir\\...\@$y/$m/$d:01:00:00 >nul 2>&1";
  16. $hl2cmd="$rdir\\hl2 -game $mod -sw +map $map -makedevshots -dev -width 1024 -height 768";
  17. print "Taking shots for $m/$d/$y\n";
  18. print "$p4cmd\n";
  19. print `$p4cmd`;
  20. print "hl2cmd\n";
  21. print `$hl2cmd`;
  22. }
  23. sub printargs
  24. {
  25. print STDERR "format is SHOTMAKER.PL rootdir mapname mod startdate enddate dateincrement\n";
  26. print STDERR "ex:\nSHOTMAKER u:\\dev\\valvegames\\main\\game ep1_c17_01 episodic 2005/10/01 2005/10/05 7\n";
  27. die;
  28. }
  29. # Toby Thurston --- 12 May 2003
  30. use strict;
  31. use Carp;
  32. use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS @mon @dom);
  33. require Exporter;
  34. @ISA = qw(Exporter);
  35. $VERSION = '0.03';
  36. =head1 NAME
  37. Cal::Date - a simple set of calendar functions for Perl
  38. (yes, yes, I know about L<Date::Calc> and L<Date::Manip> but mine is
  39. simpler, and nicer :-).
  40. =head1 SYNOPSIS
  41. use Cal::Date qw(DJM MJD today);
  42. $date = $ARGV[0] || today();
  43. print "$date --> " . MJD($date) . "\n";
  44. print "Day after -->" . DJM(MJD($date)+1) . "\n";
  45. =head1 DESCRIPTION
  46. A simple compact interface to some simple calendar routines.
  47. Implemented purely in Perl, no need for external C code etc.
  48. =head1 FUNCTIONS
  49. No functions are exported by default.
  50. =cut
  51. @EXPORT = qw();
  52. =pod
  53. The following functions can be exported from the C<Cal::Date> module:
  54. MJD DJM
  55. Easter old_style_Easter orthodox_Easter
  56. ISO_week ISO_day ISO_week_and_day
  57. day_of_year days_to_go
  58. days_in_month
  59. UK_tax_week UK_tax_month
  60. working_days
  61. today now
  62. J2G
  63. v_date r_date
  64. adjust_to_local_time adjust_to_UTC
  65. is_a_date
  66. =cut
  67. @EXPORT_OK = qw(
  68. MJD DJM
  69. Easter old_style_Easter orthodox_Easter
  70. ISO_week ISO_day ISO_week_and_day
  71. day_of_year days_to_go
  72. days_in_month
  73. UK_tax_week UK_tax_month
  74. working_days
  75. today now
  76. J2G
  77. v_date r_date
  78. adjust_to_local_time adjust_to_UTC
  79. is_a_date
  80. );
  81. =pod
  82. You can import all of them at once with
  83. C<use Cal::Date ':all';>
  84. =cut
  85. %EXPORT_TAGS = (all => [@EXPORT_OK]);
  86. =over 4
  87. =item MJD(yyyymmdd) or MJD(y,m,d)
  88. MJD returns the `modified julian day' number for a date.
  89. This is suitably small integer that you can use as the basis of many
  90. date calculations. You can call C<MJD()> with a single 8 digit string
  91. representing a date in compact ISO form, C<yyyymmdd>, or with three integers
  92. representing year, month and day of the month.
  93. Unlike the values returned from the C<gmtime()> etc. functions,
  94. year is the full AD year and month 1 is January. Other than checking
  95. that the arguments are whole numbers, the internal function C<_getYMD>
  96. does no range checking. This is a feature rather than a bug. It means
  97. you can use 0 as a month number to refer to December in the previous year,
  98. and 13 to refer to January in the next year. For example,
  99. assuming C<$month == 12>, the following are equivalent:
  100. MJD(19991301);
  101. MJD(1999, $month+1, 1);
  102. MJD(20000101);
  103. MJD(2000, 1, 1);
  104. You can do the same trick with the day numbers too; this provides a handy
  105. way to refer to the last day of the previous month. Thus C<MJD(20000100)>
  106. refers to 31 December 1999 (but note that C<MJD(20000000)> refers to
  107. 30 November 1999). This works with leap years too (of course) so
  108. C<MJD($y,3,0)> refers to the last day of February for any value of C<$y>.
  109. =cut
  110. sub MJD { # returns mjd from yyyymmdd or y,m,d
  111. use integer;
  112. my ($y, $m, $d) = &_getYMD;
  113. # allow month to be enormous
  114. while ( $m > 12) { $m -= 12; $y++ }
  115. # adjust the month/year to make it a date after 1 March
  116. if ($m < 3) { $m += 12; $y-- }
  117. # work out days upto and including the day before the previous 1 March
  118. # year * 365 + leap days - 306
  119. # we are using the (possibly proleptic) Gregorian calendar
  120. my $mjd = $y*365 + $y/4 - $y/100 + $y/400 - 306;
  121. # add days since previous 1 March (incl)
  122. $mjd += ($m+1)*306/10 - 122 + $d;
  123. # adjust so 0 == 18 Nov 1858 == JD 2,400,000.5
  124. $mjd -= 678576;
  125. return $mjd;
  126. }
  127. =item DJM(mjd)
  128. This function is the inverse of the C<MJD()> function, hence the rather
  129. cute name. It takes any number, interprets it as an MJD number and returns
  130. the corresponding date in the ISO compact form of YYYYMMDD. This form has
  131. the advantage of being easily sorted and compared.
  132. C<DJM()> is often used in combination with MJD. For example to `correct'
  133. a date use C<DJM(MJD(yyyymmdd))>. If your input date was 20000300, this will
  134. return 20000229. This idiom can also be used to check that an input date is
  135. valid. Like this:
  136. if ($date ne DJM(MJD($date)) ) {
  137. print "$date is not a valid YYYYMMDD date\n";
  138. }
  139. When you pass a real number to C<DJM()> the fractional part is interpreted
  140. as a fraction of a day, and the date and time are returned in C<YYYYMMDD HH:MM>
  141. form. Like this:
  142. print DJM(51455.7356) . "\n"; # prints 19991004 17:39
  143. If you call C<DJM()> in a list context then the parts of the date/time
  144. are returned as elements of a list, like this:
  145. ($y, $m, $d, $hr, $min) = DJM(51455.7356);
  146. ($y, $m, $d) = DJM(51500);
  147. =cut
  148. sub DJM { # returns yyyymmdd from mjd
  149. return unless defined wantarray; # don't bother doing more
  150. # the supplied MJD may be integer (hour=midnight) or real
  151. # the fractional part repesents the time of day
  152. my $mjd = shift;
  153. # convert to full Julian number
  154. my $jd = $mjd + 2400000.5;
  155. # jd0 is the Julian number for noon on the day in question
  156. # for example mjd jd jd0 === mjd0
  157. # 3.0 ...3.5 ...4.0 === 3.5
  158. # 3.3 ...3.8 ...4.0 === 3.5
  159. # 3.7 ...4.2 ...4.0 === 3.5
  160. # 3.9 ...4.4 ...4.0 === 3.5
  161. # 4.0 ...4.5 ...5.0 === 4.5
  162. my $jd0 = int($jd+0.5);
  163. # next we convert to Julian dates to make the rest of the maths easier.
  164. # JD1867217 = 1 Mar 400, so $b is the number of complete Gregorian
  165. # centuries since then. The constant 36524.25 is the number of days
  166. # in a Gregorian century. The 0.25 on the other constant ensures that
  167. # $b correctly rounds down on the last day of the 400 year cycle.
  168. # For example $b == 15.9999... on 2000 Feb 29 not 16.00000.
  169. my $b = int(($jd0-1867216.25)/36524.25);
  170. # b-int(b/4) is the number of Julian leap days that are not counted in
  171. # the Gregorian calendar, and 1402 is the number of days from 1 Jan 4713BC
  172. # back to 1 Mar 4716BC. $c represents the date in the Julian calendar
  173. # corrected back to the start of a leap year cycle.
  174. my $c = $jd0+($b-int($b/4))+1402;
  175. # d is the whole number of Julian years from 1 Mar 4716BC to the date
  176. # we are trying to find.
  177. my $d = int(($c+0.9)/365.25);
  178. # e is the number of days from 1 Mar 4716BC to 1 Mar this year
  179. # using the Julian calendar
  180. my $e = 365*$d+int($d/4);
  181. # c-e is now the remaining days in this year from 1 Mar to our date
  182. # and we need to work out the magic number f such that f-1 == month
  183. my $f = int(($c-$e+123)/30.6001);
  184. # int(f*30.6001) is the day of the start of the month
  185. # so the day of the month is the difference between that and c-e+123
  186. my $day = $c-$e+123-int(30.6001*$f);
  187. # month is now f-1, except that Jan and Feb are f-13
  188. # ie f 4 5 6 7 8 9 10 11 12 13 14 15
  189. # m 3 4 5 6 7 8 9 10 11 12 1 2
  190. my $month = ($f-2)%12+1;
  191. # year is d - 4716 (adjusted for Jan and Feb again)
  192. my $year = $d - 4716 + ($month<3);
  193. # finally work out the hour (if any)
  194. my $hour = 24 * ($jd+0.5-$jd0);
  195. if ( $hour == 0) {
  196. if (wantarray) {
  197. return ($year, $month, $day)
  198. }
  199. else {
  200. return sprintf "%d%02d%02d", ($year, $month, $day)
  201. }
  202. }
  203. else {
  204. $hour = int($hour*60+0.5)/60; # round to nearest minute
  205. my $min = int(0.5+60 * ($hour - int($hour)));
  206. $hour = int($hour);
  207. if (wantarray) {
  208. return $year, $month, $day, $hour, $min
  209. }
  210. else {
  211. return sprintf "%d%02d%02d %02d:%02d", $year, $month, $day, $hour, $min
  212. }
  213. }
  214. }
  215. =item today() or today(delta)
  216. This function returns today's date in YYYYMMDD form, saving you
  217. all that tedious mucking about with lists and C<undef>s.
  218. It uses C<localtime()> so you get the date adjusted for local time
  219. zone, depending on the time of day this may or may not be the same
  220. as the date at Greenwich. Use C<adjust_to_UTC> to get the UTC date if
  221. that's what you want.
  222. You can supply a number of days as an optional parameter. This number (which
  223. may be negative) will be added to the current date. The number should be a
  224. either a whole number of days or a week specification in a form that will
  225. match C</^[+-]?\d+[wW]\d?$/>. For example: C<1w> means one week, C<-2w3>
  226. means -17 days.
  227. =cut
  228. sub today { # return YYYYMMDD for today
  229. return unless defined wantarray;
  230. my $delta = &_get_delta;
  231. return DJM(MJD()+$delta);
  232. }
  233. sub _get_delta {
  234. my $delta = shift || 0;
  235. if ($delta =~ /^([+-])?(\d+)[wW](\d)?$/) {
  236. local $^W=0; # disable warnings for unitialized $1 or $3
  237. $delta = $1.($2*7+$3)
  238. }
  239. if ( $delta !~ /^([+-]?\d+)$/ ) {
  240. croak "Bad value for day shift: $delta\n";
  241. }
  242. return $delta;
  243. }
  244. sub now { # return hh:mm for now
  245. return unless defined wantarray;
  246. my ($s, $m, $h) = localtime();
  247. return wantarray ? ($h,$m,$s) : sprintf("%02d:%02d:%02d", $h, $m, $s);
  248. }
  249. =item Easter(year,[delta])
  250. This function takes a year number and returns the date of Easter Sunday
  251. in YYYYMMDD form for that year. See below about valid years. The date
  252. is supposed to be the first Sunday after the calendar full moon which
  253. occurs on or after 21 March. The name Easter comes from the Saxon
  254. goddess of the dawn, Eostre, whose festival was celebrated at the vernal
  255. equinox.
  256. You can supply a number of days as an optional parameter. This number
  257. (which may be negative) will be added to the resulting date. This is
  258. handy for working out dates that depend on Easter. For example:
  259. $y = 2000;
  260. $s = Easter($y,-47); # Shrove Tuesday (Pancake Day)
  261. $m = Easter($y,-21); # Mothers day in the UK
  262. $a = Easter($y,+39); # Ascension day
  263. The format of the number should be as described above under L<today()>.
  264. The algorithm used was adapted from D. E. Knuth I<Fundamental
  265. Algorithms>, as Knuth notes it is derived from older sources, and is
  266. only valid after 1582 when the Gregorian calendar was first used in
  267. Europe (but not in Britain). For years before this use the
  268. L<old_style_Easter()> routine below, which returns Julian dates such as
  269. were in use then. I have only validated this routine back to 1066, the
  270. earliest I could find a list in my reference books at home, but it
  271. should be valid further back. I do not know when Easter was first
  272. celebrated as Easter.
  273. =cut
  274. sub Easter {
  275. return unless defined wantarray; # don't bother doing more
  276. use integer;
  277. my $y = shift;
  278. my $delta = &_get_delta;
  279. my $golden = $y%19 + 1;
  280. my $century = $y/100 + 1;
  281. my $x = 3*$century/4 - 12;
  282. my $q = 5*$y/4 - $x - 10;
  283. my $epact = (11*$golden + 15 + (8*$century + 5)/25 - $x) % 30;
  284. ++$epact if ($epact == 25 && $golden > 11) || $epact == 24;
  285. my $d = 44 - $epact;
  286. $d += 30 if $d < 21;
  287. $d = $d + 7 - (($q+$d)%7);
  288. return DJM(MJD($y,3,$d)+$delta);
  289. }
  290. =item old_style_Easter(year,[delta])
  291. This function is mainly of historical interest. Before the switch to
  292. Gregorian dates that happened in 1582 in certain parts of Roman Catholic
  293. Europe, the Julian calendar was used. This routine gives you the date
  294. of Easter in the Julian calendar. Because of the way Easter is derived,
  295. this is not a constant number of days apart from the date in Gregorian.
  296. Typically it can be either 4 or 5 weeks or just a few days.
  297. In British historical records between 1582 and 1752 (when Britain
  298. switched) the Julian dates are referred to as `old style' and the
  299. Gregorian dates as `new style'. Hence my name for this function. This
  300. algorithm is based on details found on the web which referred to the
  301. algorithm of Oudin (1940), quoted in I<Explanatory Supplement to the
  302. Astronomical Almanac>, P. Kenneth Seidelmann, editor.
  303. You can add an optional day shift number as above in L<Easter()>.
  304. =cut
  305. sub old_style_Easter {
  306. return unless defined wantarray; # don't bother doing more
  307. use integer;
  308. my $y = shift;
  309. my $delta = &_get_delta;
  310. my $g = $y % 19;
  311. my $i = (19*$g + 15) % 30;
  312. my $j = ($y + $y/4 + $i) % 7;
  313. my $l = $i - $j;
  314. my $m = 3 + ($l + 40)/44;
  315. my $d = $l + 28 - 31*($m/4);
  316. return DJM(MJD($y,$m,$d)+$delta);
  317. }
  318. =item orthodox_Easter(year,[delta])
  319. The various Orthodox parts of the Christian church (principally in Greece, the
  320. Balkans and other parts of eastern Europe and Russia) still use the Julian calendar
  321. (the `old style') to work out the date of Easter, but they express the result
  322. in new style, Gregorian dates. This routine may be handy if you belong to such
  323. a church or if you are planning a spring holiday in Greece, where Easter is always
  324. a special time.
  325. This is essentially just old_style_Easter corrected to Gregorian dates with the L<J2G()> function.
  326. =cut
  327. sub orthodox_Easter {
  328. my ($y,$m,$d) = &old_style_Easter;
  329. return DJM(MJD($y,$m,$d)+J2G($y,$m,$d));
  330. }
  331. =item ISO_week(yyyymmdd) or ISO_week(y,m,d)
  332. This function returns the week number according to the ISO standard.
  333. This states that weeks begin on a Monday (day 1), and that the first
  334. week of a year is the one with 4 Jan in it. The function returns the
  335. date in the ISO week form: yyyy-Wnn. The year is included as it may
  336. differ from the year of the date in yyyymmdd form. For example
  337. C<ISO_week(20000101)> returns C<1999-W52>.
  338. The ISO day number for a given date is given by C<ISO_day()>. See below.
  339. =cut
  340. sub ISO_week {
  341. return unless defined wantarray; # don't bother doing more
  342. use integer;
  343. my ($y, $m, $d) = &_getYMD;
  344. my $jan1 = MJD($y,1,1);
  345. my $week = (MJD($y,$m,$d) - $jan1 + 1 + ($jan1+5)%7 + 3) / 7;
  346. if ( $week == 0 ) {
  347. # week belongs to last year
  348. $y--;
  349. # work out if its W52 or W53
  350. $jan1 = MJD($y,1,1);
  351. $week = (MJD($y,12,31) - $jan1 + 1 + ($jan1+5)%7 + 3) / 7;
  352. }
  353. elsif ( $week == 53 ) {
  354. # week might belong to next year
  355. # if 31 Dec is Weds or earlier
  356. if (ISO_day(MJD($y,12,31)) < 4) {
  357. $y++;
  358. $week = 1;
  359. }
  360. }
  361. return wantarray ? ($y, $week) : sprintf "%d-W%02d", $y, $week;
  362. }
  363. =item ISO_day(mjd)
  364. This function returns the ISO day number for a given MJD value.
  365. According to ISO, Monday is day 1 and Sunday day 7 in the week.
  366. To find today's ISO day number do:
  367. print ISO_day(MJD(today()));
  368. I occasionally find that I call this with a date by mistake for an MJD
  369. number, so as a convenience if the MJD number is over 10,000,000 we will
  370. interpret it as a date. This means that ISO_day won't work for dates
  371. after 29237-12-12, which we can probably live with, but that
  372. c<ISO_day(20010117)> gives a less astonishing result.
  373. =cut
  374. sub ISO_day {
  375. my $mjd = shift;
  376. if ($mjd > 10_000_000) {
  377. $mjd = MJD($mjd);
  378. }
  379. if ($mjd > -3) {
  380. return ($mjd+2)%7+1;
  381. }
  382. else {
  383. return abs(9+$mjd%7)%7+1;
  384. }
  385. }
  386. =item ISO_week_and_day(yyyymmdd) or ISO_week_and_day(y,m,d)
  387. Converts a given date to ISO Week.Day form, sometimes known as business
  388. date form. For example 19991215 maps to 1999-W51-6
  389. =cut
  390. sub ISO_week_and_day {
  391. return unless defined wantarray; # don't bother doing more
  392. return wantarray ? (&ISO_week, ISO_day(&MJD)) : &ISO_week . '-' . ISO_day(&MJD)
  393. }
  394. =item day_of_year(yyyymmdd) or day_of_year(y,m,d)
  395. This function returns the day number of the current year, where Jan 1 = 1,
  396. Feb 1 = 32 etc. It is implemented simply as
  397. MJD($y,$m,$d) - MJD($y-1,12,31)
  398. =cut
  399. sub day_of_year {
  400. my ($y, $m, $d) = &_getYMD;
  401. return MJD($y,$m,$d)-MJD($y,1,0);
  402. }
  403. =item days_to_go(yyyymmdd) or days_to_go(y,m,d)
  404. This function returns the days to the end of the year, where Dec 31 = 0,
  405. Dec 30 = 1, etc. Again it is simply implemented as
  406. MJD($y,12,31)-MJD($y,$m,$d);
  407. =cut
  408. sub days_to_go {
  409. my ($y, $m, $d) = &_getYMD;
  410. return MJD($y,12,31)-MJD($y,$m,$d);
  411. }
  412. =item days_in_month(y,m)
  413. This function returns the days in the current month. It is implemented
  414. like this:
  415. MJD($y,$m+1,1)-MJD($y,$m,1);
  416. Note that this works even in December (when C<$m==12>)
  417. because C<MJD()> interprets 13 to mean January next year.
  418. You may find it easier to use MJD directly for this function, and save
  419. an import.
  420. =cut
  421. sub days_in_month {
  422. my ($y, $m) = @_;
  423. return MJD($y,$m+1,1)-MJD($y,$m,1);
  424. }
  425. =item UK_tax_week(yyyymmdd) or UK_tax_week(y,m,d)
  426. This function is specific to UK Income Tax or `Pay As You Earn' rules.
  427. It returns a string indicating the week in the tax year corresponding to a
  428. given date. The UK tax year starts on April 5 each year. Example:
  429. print UK_tax_week(19991225); # Prints: PAYE Week 38
  430. =cut
  431. sub UK_tax_week {
  432. my ($y, $m, $d) = &_getYMD;
  433. my $april6 = MJD($y,4,6);
  434. my $today = MJD($y,$m,$d);
  435. if ($april6 > $today ) { $april6 = MJD($y-1,4,6) }
  436. use integer;
  437. return sprintf "%d", ($today-$april6)/7+1;
  438. }
  439. =item UK_tax_month()
  440. This function is also specific to UK Income Tax or `Pay As You Earn' rules.
  441. It returns a string indicating the month in the tax year corresponding to a
  442. given date. The UK tax year starts on April 5 each year. Example:
  443. print UK_tax_month(19991225); # Prints: PAYE Month 9
  444. =cut
  445. sub UK_tax_month {
  446. my ($y, $m, $d) = &_getYMD;
  447. return sprintf "%d", ($m+8-($d<6))%12+1;
  448. }
  449. =item working_days(y,m,d,period) or working_days(y,m,d,y2,m2,d2)
  450. This function returns the number of working days in a given period including
  451. start day. Call it with a date and a number of days or with two dates. The
  452. number of days returned is simply the number of non-weekend days, no account
  453. is taken of holidays etc. More sophisticated functions can be found in the
  454. C<Date::Manip> package. The two dates can be given in either order. Should
  455. they be the same, then 1 or 0 may be returned depending on whether the day in
  456. question was a working day or not.
  457. =cut
  458. sub working_days {
  459. my ($start,$end,$m,$count);
  460. $start = MJD($_[0],$_[1],$_[2]);
  461. if (@_ == 4) { $end = $start + $_[3] - 1; }
  462. elsif (@_ == 6) { $end = MJD($_[3],$_[4],$_[5]); }
  463. else { croak "Bad call to working days: $!\n" }
  464. if ($start > $end ) { ($start,$end) = ($end,$start)}
  465. if ($end-$start > 10000 ) { return 'Lots' }
  466. $count = 0;
  467. for $m ($start..$end) {
  468. ++$count if ISO_day($m) < 6
  469. }
  470. return $count;
  471. }
  472. =item v_date(year,datespec[,delta])
  473. v_date returns a date as a real MJD (or (y,m,d,h,min,s) in list context)
  474. optionally shifted by delta days, based on the specification in datespec
  475. and the given year.
  476. The format of the delta number should be as described above under L<today()>.
  477. This specification can be one of the standard variable date forms used in
  478. setting a Posix TZ environment variable, extended as noted here.
  479. The main form is Mmm.w.d where `mm' is the month (1-12) number, `w' is the
  480. week of the month (1-5 or L) note that 5 and L are equivalent and refer to
  481. the last week of the months (either the fourth or fifth depending on the
  482. length of the month), and `d' is the day of the week (0-7) where 1 = Monday
  483. and 7 (or 0) = Sunday.
  484. The use of L and 7 above are extensions to the Posix rules. Further you can
  485. extend the meaning of `d' to allow you to specify for example the last working
  486. day in a month. You do this by adding to the d number, eg:
  487. M10.L.12345 means the last working day of October, while
  488. M1.1.67 means the first weekend day in January.
  489. Other forms are...
  490. - Jddd which refers to the day of the year, regardless of leap days (ie 1
  491. March is always day J60 etc).
  492. - ddd which refers to the day of the year counting leap days, (ie day 60 is
  493. Feb 29 in leap years or Mar 1 in non-leap years.
  494. - Dmm.d.w which is exactly the same as the M form, but with the w and d
  495. fields reversed.
  496. Any of the specs may be followed by "/hh[:mm[:ss]]" to indicate a particular
  497. time.
  498. v_date returns undef if called with an invalid spec.
  499. =cut
  500. sub v_date {
  501. return unless defined wantarray;
  502. my $y = shift;
  503. my $spec = shift;
  504. my $delta = &_get_delta;
  505. my ($m,$w,$d,$mjd,$time,$dshift);
  506. # remove any time from spec
  507. if ( $spec =~ /(.*)\/(\d+)(:(\d+)(:(\d+))?)?/ ) {
  508. $time = $2;
  509. if ( defined($4) ) {
  510. $time += $4/60;
  511. if ( defined($6) ) {
  512. $time += $6/3600;
  513. }
  514. }
  515. $spec = $1;
  516. }
  517. else { $time = 0 }
  518. # change D.... to M....
  519. if (($m,$d,$w) = $spec =~ /^D([0-1]?\d).([0-7]+).([1-5L])$/ ) {
  520. $spec = "M$m.$w.$d";
  521. }
  522. # Mmm.w.d
  523. if (($m,$w,$d) = $spec =~ /^M([0-1]?\d).([1-5L]).([0-7]+)$/ ) {
  524. if ($w =~ /[1-4]/ ) {
  525. $mjd = MJD($y,$m,1) + 7*($w-1);
  526. $dshift = 7;
  527. for my $n ( split(/ */,$d)) {
  528. $n = $n - ISO_day($mjd);
  529. if ($n<0) { $n += 7 }
  530. if ($n<$dshift) { $dshift = $n }
  531. }
  532. }
  533. else { # 5 or L
  534. $mjd = MJD($y,$m+1,0);
  535. $dshift = 7;
  536. for my $n ( split(/ */,$d) ) {
  537. $n = $n - ISO_day($mjd)%7;
  538. if ($n>0) { $n -= 7 }
  539. if (abs($n)<abs($dshift)) { $dshift = $n }
  540. }
  541. }
  542. $mjd = $mjd+$dshift+$delta;
  543. }
  544. # Jnnn ....
  545. elsif (($d) = $spec =~ /^J(\d+)$/ ) {
  546. if ($d>59) { $mjd = MJD($y,3,1)+$d-60+$delta }
  547. else { $mjd = MJD($y,1,0)+$d+$delta }
  548. }
  549. # nnn ...
  550. elsif (($d) = $spec =~ /^(\d+)$/ ) {
  551. $mjd = MJD($y,1,0)+$d+$delta
  552. }
  553. else {
  554. croak "Malformed spec for v_date: $spec\n";
  555. }
  556. $mjd += $time/24;
  557. return wantarray ? DJM($mjd) : $mjd;
  558. }
  559. =item r_date(dow[,every[,start[,end]]])
  560. This routine generates a list of MJD integers corresponding to a set of
  561. repeating dates defined by the argument list. The set may be empty in which
  562. case an empty list is returned. In the scalar context you get the number of
  563. dates in the list. The list is returned sorted in ascending numerical order.
  564. dow: should match C</\d/ & /^1?2?3?4?5?6?7?$/>, that is at least one and
  565. at most seven digits between 1 and 7 with no repetitions. So "1" means
  566. Mondays, "6" means Saturdays, "14" means Mondays and Thursdays and so on.
  567. every: 1 means every dow, 2 means every other dow, 3 means every third dow, etc.
  568. Every defaults to 1.
  569. start: is a date in yyyymmdd form. The first date in the returned list
  570. will be on or after this date. Start defaults to Jan 1st in the current year.
  571. end: is another date in yyyymmdd form. The last date in the returned list
  572. will be on or before this date. End defaults to Dec 31st in the current year.
  573. Some examples:
  574. r_date(1) returns a list of every Monday in the current year
  575. r_date(2,2,20030101,20030700)
  576. returns every other Tuesday in the first half of 2003
  577. r_date(15,1,20030501,20030531)
  578. every Monday and Friday in June 2003
  579. =cut
  580. sub r_date {
  581. return unless defined wantarray;
  582. my (undef,undef,undef,undef,undef,$y) = localtime;
  583. my $days = shift;
  584. my $every = shift;
  585. my $start = shift;
  586. my $end = shift;
  587. return undef unless defined $days && $days =~ /\d+/ && $days =~ /^1?2?3?4?5?6?7?$/;
  588. $every = 1 unless defined $every && $every =~ /^\d+$/ && $every<100;
  589. if ( defined $start && $start=~/^\d{8}$/ ) { $start = MJD($start) }
  590. else { $start = MJD($y,1,1) }
  591. if ( defined $end && $end =~/^\d{8}$/ ) { $end = MJD($end) }
  592. else { $end = MJD($y,12,31) }
  593. my @list = ();
  594. for my $dow ( split / */, $days) {
  595. my $day_shift = $dow - ISO_day($start);
  596. $day_shift += 7 if $day_shift < 0;
  597. my $first_date = $start + $day_shift;
  598. for (my $i=0; $first_date+$i<$end; $i+=7*$every) {
  599. push @list, $first_date+$i;
  600. }
  601. }
  602. return sort @list;
  603. }
  604. =item adjust_to_local_time(mjd,tzoffset,tzrule1,tzrule2[,DST_delta])
  605. This routine takes a real MJD number --- representing a UTC date and time ---
  606. and adjusts it for time zone making proper allowance for summer time or
  607. `daylight saving time' (DST). The second argument is the normal difference
  608. between UTC and local time (ie New York = +5) in hours.
  609. The third and fourth arguments are two rules that define when DST should
  610. start when it should stop. If the rules are empty or undefined then the
  611. routine returns the MJD adjusted to local time with no allowance for summer
  612. time. The rules are rules in the format understood by C<v_date()>.
  613. The fifth argument represents the number of hours that the clocks go forward
  614. when DST starts. If this is omitted it will default to 1. This default was
  615. not always correct historically but as far as I have been able to verify it
  616. is currently, so you can nearly always omit the fifth argument.
  617. =cut
  618. sub adjust_to_local_time {
  619. my $mjd = shift;
  620. my $tz = shift || $Cal::Astro::tz;
  621. my $r1 = shift || $Cal::Astro::r1;
  622. my $r2 = shift || $Cal::Astro::r2;
  623. my $dst_delta = shift || 1;
  624. # stop here if no date given
  625. return '' unless defined($mjd);
  626. return '' if $mjd eq '';
  627. # stop here if no TZ given
  628. return $mjd unless defined($tz);
  629. # adjust for time zone
  630. $mjd = $mjd-$tz/24;
  631. # stop here if no summer time rules
  632. return $mjd unless defined($r1) && defined($r2);
  633. # make rules into dates for the current year
  634. my ($year) = DJM($mjd);
  635. my $d1 = v_date($year,$r1);
  636. my $d2 = v_date($year,$r2);
  637. # are we in DST at the start of the year?
  638. # (ie does r1 say October rather than March/April)
  639. my $jan_state = ($d1 > $d2);
  640. # swap the dates so that d1 < d2
  641. ($d1,$d2) = ($d2,$d1) if $jan_state;
  642. # if the date is in the summer set the opposite of
  643. # the state at the start of the year & adjust if needed
  644. if ($d1 <= $mjd && $mjd < $d2 ) {
  645. return $mjd + $dst_delta/24 * !$jan_state;
  646. }
  647. # otherwise return the state at the start of the year
  648. return $mjd + $dst_delta/24 * $jan_state;
  649. }
  650. =item adjust_to_UTC(mjd,tzoffset,tzrule1,tzrule2[,DST_delta])
  651. This routine takes a real MJD number --- representing a local date and time ---
  652. and adjusts it back to UTC allowing for local time zone and
  653. summer time rules.
  654. The arguments are all exactly the same as those for C<adjust_to_local_time()>.
  655. =cut
  656. sub adjust_to_UTC {
  657. my $mjd = shift;
  658. my $tz = shift || $Cal::Astro::tz;
  659. my $r1 = shift || $Cal::Astro::r1;
  660. my $r2 = shift || $Cal::Astro::r2;
  661. my $dst_delta = shift || 1;
  662. return adjust_to_local_time($mjd,-$tz,$r1,$r2,-$dst_delta);
  663. }
  664. sub is_a_date {
  665. my $date = shift;
  666. $date =~ s/[^0-9]//g;
  667. return 0 unless $date =~ /\d{8}/;
  668. return $date eq DJM(MJD($date));
  669. }
  670. sub _getYMD {
  671. my ($y, $m, $d);
  672. if ( @_ == 0 ) {
  673. (undef, undef, undef, $d, $m, $y) = localtime();
  674. $y += 1900;
  675. $m ++;
  676. } elsif ( @_ == 1 && !defined $_[0] ) {
  677. my ($package, $filename, $line) = caller;
  678. croak "\nCal::Date routine called with undefined value by $package \nLook at $filename, line $line\n";
  679. } elsif ( @_ == 1 && $_[0] =~ /^\d+$/ && $_[0] > 100000 ) {
  680. $y = substr($_[0],0,-4);
  681. $m = substr($_[0],-4,2);
  682. $d = substr($_[0],-2);
  683. } elsif ( @_ == 1 && $_[0] =~ /^\d+$/ ) {
  684. # probably an MJD as it is so small
  685. ($y, $m, $d) = DJM($_[0]);
  686. } elsif (@_ == 1 && $_[0] =~ /^(\d\d\d\d)-(\d\d)-(\d\d)$/ ) {
  687. ($y, $m, $d) = ($1, $2, $3);
  688. } elsif ( @_ == 3
  689. && $_[0] =~ /^\d+$/
  690. && $_[1] =~ /^[-]?\d+$/
  691. && $_[2] =~ /^[-]?\d+$/) {
  692. ($y, $m, $d) = @_
  693. } else {
  694. croak "Can't read a date from this --> [@_]"
  695. }
  696. return ($y, $m, $d);
  697. }
  698. sub J2G { # returns days difference between julian on gregorian dates
  699. use integer;
  700. my ($y, $m, $d) = &_getYMD;
  701. # if the month is Jan or Feb then use the year before
  702. if ($m < 3) { $y-- }
  703. # the difference in leap days is just the omitted century end leap days in the
  704. # Gregorian calendar, less two because they didn't start until
  705. # some long time after 1 AD
  706. return $y/100 - $y/400 - 2;
  707. }
  708. =back
  709. =head1 SEE ALSO
  710. L<Date::Calc> and L<Date::Manip> packages which provide more comprehensive
  711. functions; as they say: there's more than one way to do it.
  712. =head1 AUTHOR
  713. Toby Thurston
  714. web: http://www.wildfire.dircon.co.uk
  715. =cut
  716. 1;