Page 1 of 1

Rewriting calendar display classes

Posted: Mon Apr 02, 2007 10:29 am
by Luke
A few months back, I wrote a small set of classes to render an events calendar I wrote. I'd like to rewrite these classes from the ground up using test-driven development. Right now, the system uses unix timestamps and the date() and getdate() functions. I'd like for the classes to have the widest range of dates possible and provide the best flexibility and most ease of use. Now, with all of that in mind, what would you guys suggest I use? Still timestamps? Something else?

Thanks! :D

Posted: Mon Apr 02, 2007 10:41 am
by feyd
I wrote this a while ago. I can't supply the ancillary components, but those parts should be, overall, fairly explanatory to convert to normal forms. I don't recall offhand if I finished the todo.

Code: Select all

<?php

/**
 * @todo convert this to a Super-Julian form, thus allowing far larger range of
 * dates. Time of day is stored in microseconds for insane accuracy. This would
 * require versions of all date/time related functions available from php...
 */

/**
 * TDateTime
 * @package canons
 * @subpackage time
 * @author feyd
 * @version $Id $
 */
class TDateTime extends TObject {
  /**
   * @var MonthFullNames array full names of months
   * @access static
   */
  static $MonthFullNames = array(
    1=>"January",  "February",   "March",
    "April",    "May",        "June",
    "July",     "August",     "September",
    "October",  "November",   "December",
  );

  /**
   * @var MonthShortNames array short names of months
   * @access static
   */
  static $MonthShortNames = array(
    1=>"Jan", "Feb", "Mar",
    "Apr", "May", "Jun",
    "Jul", "Aug", "Sep",
    "Oct", "Nov", "Dec",
  );

  /**
   * @var DayFullNames array full names of days
   * @access static
   */
  static $DayFullNames = array(
    "Sunday", "Monday", "Tuesday",
    "Wednesday", "Thursday", "Friday", "Saturday"
  );

  /**
   * @var DayShortNames array short names of days
   * @access static
   */
  static $DayShortNames = array(
    "Sun", "Mon", "Tue",
    "Wed", "Thu", "Fri", "Sat"
  );

  /**
   * @var DayTable array lengths of months for standard and leap years
   * @access static
   */
  static $DayTable = array(
    array(1=>31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
    array(1=>31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
  );
  
  static $DayToHours            = '24';       //  hours in a day
  static $HourToMinutes         = '60';       //  minutes in an hour
  static $MinuteToSeconds       = '60';       //  seconds in a minute
  static $SecondToMicroseconds  = '1000000';  //  microseconds in a second

  static $J0000 = '1721424.5'; // Julian date of Gregorian epoch: 0000-01-01
  static $J0001 = '1721425.5'; // Gregorian epoch
  static $J1970 = '2440587.5'; // Julian date at Unix epoch: 1970-01-01
  static $JMJD  = '2400000.5'; // Epoch of Modified Julian Date system
  static $J1900 = '2415020.5'; // Epoch (day 1) of Excel 1900 date system (PC)
  static $J1904 = '2416480.5'; // Epoch (day 0) of Excel 1904 date system (Mac)
  
  /**
   * TDateTime::__construct
   * @return void
   */
  function __construct() {
    parent::__construct();
    
    $this->AddPropertyEx('Format', vtString, scPublished, false, true);
    $this->SetDefault('Format',translate('TIMESTAMP'));
    $this->AddPropertyEx('Timezone', vtString, scPublished, false, true);
    $this->AddPropertyEx('TimezoneOffset', vtFloat, scPublished, false, true);
    //$this->ChangeTimezone('UTC');
  }

  /**
   * TDateTime::Now
   * retrieve the current time
   * @return string formatted current time
   */
  static function Now() {
    if(isStaticCall()) {
      //  use system default, since static version has no internals
      return self::Date(translate('TIMESTAMP'));
    } else {
      return $this->Date($this->Format);
    }
  }
  
  /**
   * TDateTime::mktime
   * similar to php's internal mktime function, except the return is a super-size
   * julian time mark.
   * @return string TDateTime string value
   */
  function mktime() {
    //  do some calculations to set up the data
    $data = array();
    $names = array('hour','minute','second','month','day','year','is_dst','microsecond');
    $nnames = count($names);
    $chars = array('H','i','s','m','d','Y');
    $nchars = count($chars);
    $args = (func_num_args() == 1 && is_array(func_get_arg(0)) ? func_get_arg(0) : func_get_args());
    for($i = 0; $i < $nnames; $i++) {
      if(isset($args[$i]) and $args[$i] !== null) {
        $data[] = $args[$i];
      }
      elseif(isset($args[$names[$i]]) and $args[$names[$i]] !== null) {
        $data[] = $args[$names[$i]];
      }
      elseif($i < $nchars) {
        $data[] = self::idate($chars[$i]);
      } elseif($i == $nchars) {
        //  daylight time
        //  @todo add support for auto-detection based toggling
        $data[] = false;
      } elseif( $i == $nnames-1 ) {
        //  microseconds
        $data[] = array_shift(explode(' ',microtime()));
      }
    }
    
    if(function_exists('bcmul')) {
      $p = 0;

      $m = $data[3];
      $d = $data[4];
      $y = $data[5];
      $h = $data[0];
      $i = $data[1];
      $s = $data[2];
      $l = $data[6];
      $u = $data[count($data)-1];
      self::CorrectDateTime($m,$d,$y,$h,$i,$s,$l,$u);

      //  store off the corrected date
      $cm = $m;
      $cd = $d;
      $cy = $y;

      if(bccomp($m,'3',$p) < 1) {
        $m = bcadd($m,'12',$p);
        $y = bcsub($y,'1',$p);
      }
      
      $A = bcdiv($y,'100',$p);
      $B = bcdiv($A,'4',$p);
      $C = bcadd(bcsub('2',$A,$p),$B,$p);
      $E = bcmul('365.25',bcadd($y,'4716',$p),$p);
      $F = bcmul('30.6001',bcadd($m,'1',$p),$p);
      $jd = bcadd($C,$d,$p);
      $jd = bcadd($jd,$E,$p);
      $jd = bcadd($jd,$F,$p);
      $jd = bcsub($jd,'1524.5',1);

      $p = 200;
      $t = $u;
      
      //  correct the time to UTC, the internal time zone.
      $s = bcsub($s,date('Z'),$p);

      $t = bcadd($t,$s,$p);
      $t = bcdiv($t,self::$MinuteToSeconds,$p);

      $t = bcadd($t,$i,$p);
      $t = bcdiv($t,self::$HourToMinutes,$p);

      $t = bcadd($t,$h,$p);
      $t = bcdiv($t,self::$DayToHours,$p);

      $ret = bcadd($jd,$t,$p);
      
      return $ret;
      //echo "$cm/$cd/$cy\n\n  jd = $jd\n+  t = $t\n----------\n$ret\n\n".gregoriantojd($cm,$cd,$cy)."\n\n".jdtogregorian($jd);
    } else {
      trigger_error('BC Math is required for accurate time calculations.');
    }
  }
  
  /**
   * TDateTime::idate
   * retrieve an integer value from the date subsystem
   * @return integer the results of the idate call.
   */
  static function idate($char, $time = null) {
    if(func_num_args() > 1)
      return idate($char, intval($time));
    else
      return idate($char);
  }
  
  /**
   * TDateTime::isLeap
   * test is a given year is a leap year
   * @return boolean
   */
  static function isLeap($year) {
    //  if year is divisble by 4 it's a leap year unless it's divisble by 100,
    //  except when divisible by 400. Divisible by 4000 also isn't.
    return ((bcmod($year,'4') == 0 and bcmod($year,'100') != 0) || bcmod($year,'400') == 0 and bcmod($year,'4000') != 0);
  }
  
  /**
   * TDateTime::CorrectDateTime
   * @return void
   */
  static function CorrectDateTime(&$m,&$d,&$y,&$h,&$i,&$s,&$l,&$u) {
    $p = 200;
    $nmonths = count(self::$MonthShortNames);

    //  echo "$m/$d/$y being corrected.\n";

    //  echo "starting month correction.\n";
    //  correct month of the year
    while(bccomp('0',$m, 0) >= 0 or bccomp($m,$nmonths, 0) > 0) {
      $mdir = bccomp($m,'1', 0);
      if($mdir < 0) {
        echo "adding $nmonths to $m\n";
        $m = bcadd($m,$nmonths, 0);
        $y = bcsub($y,'1', 0);
      } else {
        echo "subtracting $nmonths from $m\n";
        $m = bcsub($m,$nmonths, 0);
        $y = bcadd($y,'1', 0);
      }
    }
    
    //echo "starting time correction\n";
    
    $second_minute  = self::$MinuteToSeconds;
    $second_hour    = bcmul(self::$HourToMinutes,$second_minute);
    $second_day     = bcmul(self::$DayToHours, $second_hour);
    
    //  bring all the times into the same timespace
    $t = bcadd(bcmul($h,$second_hour,$p),bcadd(bcmul($i,$second_minute,$p),bcadd($s,$u,$p),$p),$p);
    $dt = bcdiv( $t, $second_day, 0);
    //  and now the corrected time, which we need to bring back out to varibles
    $t = bcsub($t,$dt,$p);
    
    //  hours
    $h = bcdiv($t,$second_hour,0);
    $t = bcsub($t,bcmul($h,$second_hour,0),0);
    
    //  minutes
    $i = bcdiv($t,$second_minute,0);
    $t = bcsub($t,bcmul($i,$second_minute,0),0);
    
    //  seconds
    $s = bcdiv($t,'1',0);
    $t = bcsub($t,$i,0);
    
    //  strip any remaining seconds from the microseconds
    $u = bcsub($u,bcdiv($u,'1',0),0);
    
    //  microseconds
    //$u = bcmul($u,self::$SecondToMicroseconds,$p);

    //echo "{$h}::{$i}::".bcadd($s,$u,10);

    //  add or subtract the correct amount of days based on overflow of the time
    //  correction
    $d = bcadd($d, $dt, 0);

    //  echo "starting day correction.\n";
    //  correct the day of the month
    while(bccomp('0',$d, 0) >= 0 or bccomp($d,self::$DayTable[self::isLeap($y)][$m],0) > 0) {
      $ddir = bccomp($d,'1',0);
      if($ddir < 0) {
        //  go back a month
        //  echo "subtracting 1 from $m\n";
        $m = bcsub($m,'1',0);
        //  did we hit a wrap around on the month?
        if(bccomp($m,'0',0) == 0) {
          //  echo "$m wraps\n";
          $m = $nmonths;
          $y = bcsub($y,'1',0);
        }
        //  hopefully move the day into the new month's context
        $d = bcadd($d, self::$DayTable[self::isLeap($y)][$m],0);
      } else {
        //  hopefully move the day into the new month's context
        $d = bcsub($d, self::$DayTable[self::isLeap($y)][$m],0);

        //  bump up a month
        //  echo "adding 1 to $m\n";
        $m = bcadd($m,'1',0);
        //  did we hit a wrap around on the month?
        if(bccomp($m,$nmonths,0) > 0) {
          echo "$m wraps\n";
          $m = '1';
          $y = bcadd($y,'1',0);
        }
      }
    }
    //  echo "Final corrected: {$y}-{$m}-{$d} {$h}:{$i}:{$s}.{$u}\n";
  }
  
  /**
   * TDateTime::BreakOut
   * Break a given TDateTime string into gregorian parts (with daylight)
   * @return void
   */
  static function BreakOut($Time) {
    $p = 200;
    $Time = bcadd($Time,'0.5',$p);
    $dt = explode('.',$Time);
    $jd = $dt[0];
    $broken = array();
    if(empty($dt[1])) {
      $broken['Hour'] = 0;
      $broken['Minute'] = 0;
      $broken['Second'] = 0;
      $broken['Subsecond'] = 0;
    } else {
      $t = '0.'.$dt[1];

      $broken['Hour'] = bcmul($t,self::$DayToHours,0);
      $t = bcsub(bcmul($t,self::$DayToHours,$p),$broken['Hour'],$p);

      $broken['Minute'] = bcmul($t,self::$HourToMinutes,0);
      $t = bcsub(bcmul($t,self::$HourToMinutes,$p),$broken['Minute'],$p);

      $broken['Second'] = bcmul($t,self::$MinuteToSeconds,0);
      $t = bcsub(bcmul($t,self::$MinuteToSeconds,$p),$broken['Second'],$p);

      $t = explode('.',$t);
      $broken['Subsecond'] = (isset($t[1]) ? $t[1] : '000');
    }
    
    $p = 5;

    //  make sure $jd is aligned to a julian nychthemeron
    $wjd = bcadd(bcdiv(bcsub($jd,'0.5'),'1',0),'0.5',$p);
    $depoch = bcsub($wjd,self::$J0001,1);
    $quadricent = bcdiv($depoch,'146097',0);
    $dqc = bcmod($depoch,'146097');
    $cent = bcdiv($dqc,'36524',0);
    $dcent = bcmod($dqc,'36524');
    $quad = bcdiv($dcent,'1461',0);
    $dquad = bcmod($dcent,'1461');
    $yindex = bcdiv($dquad,'365',0);
    $year = bcadd(bcadd(bcadd(bcmul($quadricent,'400',$p),bcmul($cent,'100',$p),$p),bcmul($quad,'4',$p),$p),$yindex,0);
    if (!($cent == '4' or $yindex == '4')) {
      $year = bcadd($year,'1',0);
    }
    $yearday = bcsub($wjd,self::mktime(0,0,0,1,1,$year,false,0),0);
    $leapadj = (bccomp($wjd,self::mktime(0,0,0,3,1,$year) < 0) ? 0 : (self::isLeap($year) ? 1 : 2) );
    $month = bcdiv(bcadd(bcmul(bcadd($yearday,$leapadj,$p),'12',$p),'373',$p),'367',0);
    $day = bcadd(bcsub($wjd,self::mktime(0,0,0,$month,1,$year),$p),1,0);
    
    //$nmonths = count(self::$MonthShortNames);
    $broken['Year'] = $year;
    $broken['Month'] = $month;
    $broken['Day'] = $day;
    //$broken['month'] = bcmod(bcadd($month,$nmonths,0),bcadd($nmonths,'1',0));
    //$broken['day'] = (bccomp($month,'0') == 0 ? self::$DayTable[self::isLeap($broken['year'])][$broken['month']] : $day);
    $broken['YearDay'] = $yearday;
    $broken['Time'] = bcsub($Time,'0.5',200);
    $broken['JulianDay'] = $wjd;
    
    //  debugging
    /*
    $broken['wjd'] = $wjd;
    $broken['depoch'] = $depoch;
    $broken['quadricent'] = $quadricent;
    $broken['dqc'] = $dqc;
    $broken['cent'] = $cent;
    $broken['dcent'] = $dcent;
    $broken['quad'] = $quad;
    $broken['dquad'] = $dquad;
    $broken['yindex'] = $yindex;
    $broken['yearday'] = $yearday;
    $broken['leadadj'] = $leadadj;
    */

    //self::CorrectDateTime($broken['month'], $broken['day'], $broken['year'], $broken['hour'], $broken['minute'], $broken['second'], $l, $broken['subsecond']);

    return $broken;
  }
  
  /**
   * TDateTime::JulianWeekday
   * @return string
   */
  static function JulianWeekday($j) {
    return bcmod(bcadd($j,'1.5',0), '7');
  }
  
  /**
   * 
   * @return void
   */
  static function WeekdayBefore($weekday, $jd) {
    return $jd - self::JulianWeekday(bcsub($jd,$weekday,100));
  }
  
  /**
   * TDateTime::SearchWeekday
   * @return mixed
   */
  static function SearchWeekday($weekday, $jd, $direction, $offset) {
    return self::WeekdayBefore($weekday, bcadd($jd,bcmul($direction,$offset,0),100));
  }
  
  /**
   * TDateTime::PreviousWeekday
   * @return mixed
   */
  static function PreviousWeekday($weekday, $jd) {
    return self::SearchWeekday($weekday, $jd, -1, 1);
  }
  
  /**
   * TDateTime::NextWeekday
   * @return mixed
   */
  static function NextWeekday($weekday, $jd) {
    return self::SearchWeekday($weekday, $jd, 1, 7);
  }
  
  /**
   * TDateTime::NumWeeks
   * @return string
   */
  static function NumWeeks($weekday, $jd, $nthweek) {
    $j = bcmul(7,$nthweek,0);

    if (bccomp($nthweek,'0',0) > 0) {
      $j = bcadd($j,self::PreviousWeekday($weekday, $jd),0);
    } else {
      $j = bcadd($j,self::NextWeekday($weekday, $jd),0);
    }
    return $j;
  }
  
  /**
   * TDateTime::ISOtoJulian
   * @return void
   */
  static function ISOToJulian($week, $day, $year) {
    return bcadd($day,self::NumWeeks(0, self::mktime(12,0,0,12,28,bcsub($year,1,0),false,0), $week),1);
  }
  
  /**
   * TDateTime::JulianToISO
   * @return array elements: year, week, and day respective to numeric index.
   * Conforms to ISO 8601
   */
  static function JulianToISO($jd) {
    $year = self::BreakOut(bcsub($jd,3,1));
    $year = $year['Year'];
    if (bccomp($jd,self::ISOToJulian(1,1,bcadd($year,1,0)),1) >= 0) {
      $year = bcadd($year,1,0);
    }

    $week = bcadd(bcdiv(bcsub($jd,self::ISOToJulian(1,1,$year)),7,0),1,0);

    $day = self::JulianWeekday($jd);
    if ($day == 0) {
      $day = 7;
    }
    return array($year, $week, $day);
  }
  
  /**
   * TDateTime::date
   * @param $aFormat string the formatting string to use. Supports all PHP 5.1.0
   * changes.
   * @param $aTime string This should be a TDateTime time value, it does _not_
   * support unix timestamps
   * @return void
   */
  static function date($Format=null, $Time=null) {
    if($Format === null) {
      $Format = self::GetFormat();
    }
    if($Time === null) {
      $Time = self::GetTime();
    }
    $breakout = self::BreakOut($Time);
    $output = '';
    for($i = 0, $j = strlen($Format); $i < $j; $i++) {
      $pre = '';
      $post = '';
      $char = $Format{$i};

      switch($char) {

        case '\\':  //  next character is escaped, do not process it
          $i++;
          if($i < $j) {
            $output .= $Format{$i};
          }
        break;

        //  day related bits
        
        case 'd': //  day of month (leading zero)
          $pre = (bccomp('10',$breakout['Day'],0) > 0 ? '0' : '');
        case 'j': //  day of month (no leading zero)
          $output .= $pre.$breakout['Day'];
        break;

        case 'W': //  ISO Week
        case 'N': //  ISO Day
        case 'o': //  ISO Year
          if(!isset($iso)) {
            $iso = self::JulianToISO($breakout['JulianDay']);
          }
          if($char == 'W') {
            $output .= $iso[1];
          } elseif($char == 'N') {
            $output .= $iso[2];
          } else {
            $output .= $iso[0];
          }
        break;
        
        case 'D': //  short day name
          $output .= translate(self::$DayShortNames[self::JulianWeekday($breakout['JulianDay'])]);
        break;
        
        case 'l': //  full day name
          $output .= translate(self::$DayFullNames[self::JulianWeekday($breakout['JulianDay'])]);
        break;
        
        case 'w': //  weekday number (zero based)
          $output .= self::JulianWeekday($breakout['JulianDay']);
        break;
        
        case 'z': //  year day
          $output .= $breakout['YearDay'];
        break;

        case 'S': //  english suffix for a day: st, nd, rd, th
          $temp = bcmod($breakout['Day'],'10');
          if(bcdiv($breakout['Day'],'10',0) == '1') {
            $temp = '0';
          }
          switch($temp) {
            case '1':
              $output .= 'st';
            break;
            
            case '2':
              $output .= 'nd';
            break;
            
            case '3':
              $output .= 'rd';
            break;
            
            default:
              $output .= 'th';
            break;
          }
        break;
        
        //  month related bits
        
        case 'F': //  full name of the month
          $output .= translate(self::$MonthFullNames[$breakout['Month']]);
        break;

        case 'M':
          $output .= translate(self::$MonthShortNames[$breakout['Month']]);
        break;

        case 'm': //  month (leading zero)
          $pre = (bccomp('10',$breakout['Month']) > 0 ? '0' : '');
        case 'n': //  month (no leading zero)
          $output .= $pre.$breakout['Month'];
        break;
        
        //  year related bits
        
        case 'L': //  leap year
          $output .= (self::isLeap($breakout['Year']) ? '1' : '0');
        break;
        
        case 'Y':
          $output .= $breakout['Year'];
        break;
        
        case 'y':
          $output .= substr($breakout['Year'],-2);
        break;
        
        //  time related bits
        
        case 'a': //  am pm
        case 'A': //  AM PM
          $pre = (bccomp('11',$breakout['Hour'],0) <= 0 ? 'pm' : 'am');
          if($char == 'A') {
            $pre = strtoupper($pre);
          }
          $output .= $pre;
        break;
        
        case 'g': //  hour (no leading zero) 12 hr form
        case 'G': //  hour (no leading zero) 24 hr form
        case 'h': //  hour (leading zero) 12 hr form
        case 'H': //  hour (leading zero) 24 hr form
          $temp = $breakout['Hour'];
          if($char == 'g' or $char == 'h') {
            if(bccomp('11',$temp,0) <= 0) {
              $temp = bcsub($temp,'12',0);
            }
            $temp = bcadd($temp,'1',0);
          }
          if($char == 'h' or $char == 'H') {
            $pre = (bccomp('10',$temp,0) > 0 ? '0' : '');
          }
          $output .= $pre.$temp;
        break;
        
        case 'i': //  minutes (leading zero)
          $output .= (bccomp('10',$breakout['Minute'],0) > 0 ? '0' : '').$breakout['Minute'];
        break;
        
        case 's': //  seconds (leading zero)
          $output .= (bccomp('10',$breakout['Second'],0) > 0 ? '0' : '').$breakout['Second'];
        break;

        case 'B': //  Swatch internet time (don't really care about this)
        break;
        
        case 'e': //  verbal timezone
          //  @todo add timezone and daylight rules support
          $output .= 'Universal Coordinated Time';
        break;
        
        default:
          $output .= $char;
        break;
      }
    }
    
    return $output;
  }
  
  /**
   * TDateTime::GetFormat
   * @access private
   * @return string The "default" format to use
   */
  private static function GetFormat() {
    if(isStaticCall(1)) {
      return translate('TIMESTAMP');
    } else {
      return $this->Format;
    }
  }
  
  /**
   * TDateTime::GetTime
   * @access private
   * @return string The "default" current time
   */
  private static function GetTime() {
    if(isStaticCall(1)) {
      return self::mktime();
    } else {
      return $this->mktime();
    }
  }
  
  /**
   * TDataTime::GetTZ
   * @return void
   */
  static function GetTZ() {
    
  }
  
  //  EOF
}

?>
some tests

Code: Select all

<?php
import('time.TDateTime');
$test = new TDateTime();
/*
$y = 2005;
$m = 0;
$d = 0;
$h = idate('H');
$i = idate('i');
$s = idate('s');
$l = 0;
$u = 0;
TDateTime::CorrectDateTime($m,$d,$y,$h,$i,$s,$l,$u);
*/
$breakout = $test->BreakOut($test->mktime());
var_export($breakout);
echo "\n\n".TDateTime::$DayFullNames[TDateTime::JulianWeekday($breakout['JulianDay'])]."\n\n";

echo $test->date('\\d=d \\j=j \\W=W \\N=N \\o=o \\D=D \\l=l \\w=w \\z=z \\S=S \\F=F \\M=M \\m=m \\n=n \\L=L \\Y=Y \\y=y \\a=a \\A=A \\g=g \\G=G \\H=H \\h=h \\i=i \\s=s \\B=B \\e=e',$breakout['Time'])."\n\n";

$years = array('1600','1648','1700','1800','1900','1996','2000','4000');
$results = array();
foreach($years as $year)
$results[$year] = TDateTime::isLeap($year);

echo "\nLeap year calculations:\n";
var_export($results);

?>

Posted: Mon Apr 02, 2007 10:52 am
by Kieran Huggins
Date formats make my brain hurt!

Posted: Mon Apr 02, 2007 1:37 pm
by Xoligy
Kieran Huggins wrote:Date formats make my brain hurt!
Welcome to the club.

Posted: Mon Apr 02, 2007 3:36 pm
by Oren
feyd, I've barely read your code so I might be missing something, but is there a reason as to why you do this?

Code: Select all

$chars = array('H','i','s','m','d','Y');
$nchars = count($chars); 

Posted: Mon Apr 02, 2007 4:03 pm
by Maugrim_The_Reaper
Timestamps offer the most flexibility, but they also lose their context off the local system which is why UTC or similar make a good common format (hence it's support in iCalendar). Something along the lines of Feyd class is also very useful - just be careful of the date parameter types which vary between PHP and the relevant ISO specification (Zend_Date, another good set of classes to peek at, allows for both). Date classes inevitably end up being a parcel of reuseable code so it's worth a little extra work than usual to get right.

Small comment on feyd's class - it would be nice for a solution to support the faster alternatives to BCMath - GMP particularly is quite faster at precision calculations. It's pretty rare to see idate() in practice - kudos to the PHP wizard of Devnetwork ;).

Since it's test driven, a good place to start is taking your requirements, attempting to identify relevant classes, and designing an interface you think would work. From there you can take a base unit and start with the infamous failing test to implement a piece of that functionality ;). Assuming a Date class is a good place to start (seems reasonable) you now have a few suggestion to grab some inspiration from.

Posted: Mon Apr 02, 2007 9:07 pm
by feyd
Oren, I have no idea right now.. It's been so long since I've even looked at that code, let alone used it. (The code dates back to 2005) Looking at the code, they are the default values for the call to mktime(). They are used when no input is passed or is missing to the method.

Maugrim, the reason why I'm not using GMP is because it processes the time into the equation too since it's using Julian date storage.