Index: log4j_contrib/src/org/apache/log4j/DatedFile.java
diff -c /dev/null log4j_contrib/src/org/apache/log4j/DatedFile.java:1.1
*** /dev/null	Fri Jan  4 14:17:20 2002
--- log4j_contrib/src/org/apache/log4j/DatedFile.java	Fri Jan  4 14:05:01 2002
***************
*** 0 ****
--- 1,140 ----
+ /*
+  * Copyright (C) The Apache Software Foundation. All rights reserved.
+  *
+  * This software is published under the terms of the Apache Software
+  * License version 1.1, a copy of which has been included with this
+  * distribution in the LICENSE.APL file.  */
+ 
+ package org.apache.log4j;
+ 
+ import java.text.SimpleDateFormat;
+ import java.io.File;
+ import java.io.IOException;
+ import java.util.Calendar;
+ 
+ /**
+  * Represents the filename of the log file, including a date.
+  * Handles calendar logic, including the date formatting and
+  * determining the next rollover time.
+  * Calls to getDatedFilename should not be made by multiple threads
+  * (because SimpleDateFormat isn't thread-safe) unless synchronized by
+  * the caller.
+  *
+  * @author Jon Skeet <skeet@pobox.com>
+  * @author Jim Doyle <jdoyle@iso-ne.com>
+  */
+ public class DatedFile
+ {
+     /** Formatter to use for dates in filename; generated from datePattern */
+     private SimpleDateFormat dateFormat;
+     
+     /** Base of filename (ie everything before the extension) */
+     private String filenameBody=null;
+ 	
+     /** File extension to add after date (includes .) */
+     private String extension=null;
+ 
+ 
+     /**
+      * Constructs dated file with filename and explicit date pattern
+      * @param filename Base filename, pulled apart and used to construct
+      *                 a dated filename.  Includes directories and any extension.
+      * @param datePattern Date pattern string, as expected by
+      *                    {@link SimpleDateFormat}, like "yyyyMMdd"
+      * @exception IOException if filesystem queries needed to create
+      *                        filename canonical path fail
+      */
+     public DatedFile (String filename, String datePattern) throws IOException
+     {
+         parseFilename(filename);
+         createFormat(datePattern);
+     }
+     
+     /**
+      * Constructs a dated filename, based on the date in the given calendar.
+      * A filename of <code>body.ext</code> is translated to
+      * <code>body-date.ext</code>.  Expects <code>setFile</code> to have
+      * been called to set the body and extension.  If
+      * <code>setDatePattern</code> has not been called, the default
+      * "yyyyMMdd" will be used.
+      * @param date Calendar set to date to apply to this file.
+      *             May have any time values:
+      *             hour, minutes, seconds, and millis don't have to be cleared.
+      *             The given Calendar will be modified.
+      * @return filename including original path, base name, date, and extension
+      */
+     public String getDatedFilename (Calendar date)
+     {
+         return filenameBody+"-"+dateFormat.format (date.getTime())+extension;
+     }
+ 
+     /**
+      * Determines the next time the filename will change, using
+      * the date in the given calendar as the current time.
+      * Since the filename changes at midnight, this will be the midnight
+      * following the given time.
+      * @param date Calendar set to date containing current time.
+      * @return Milliseconds for time of next filename change
+      */
+     public long nextFilenameChange (Calendar date)
+     {
+         Calendar nextDayMidnight = Calendar.getInstance();
+         nextDayMidnight.clear();
+         nextDayMidnight.set(date.get(Calendar.YEAR),
+                             date.get(Calendar.MONTH),
+                             date.get(Calendar.DATE));
+         nextDayMidnight.add(Calendar.DATE, 1);
+         return nextDayMidnight.getTime().getTime();
+     }
+ 
+     
+     /**
+      * Pulls apart the base filename for this file.
+      * Sets the <code>filenameBody</code>
+      * and <code>extension</code>, which get used when a dated filename
+      * is created.
+      * @param filename Base filename, including directories and any extension
+      * @exception IOException if filesystem queries needed to create
+      *                        canonical path fail
+      */
+     private void parseFilename (String filename) throws IOException
+     {
+         filename = filename.trim();
+         
+         File f = new File (filename);
+         
+         // Separate the name from the parent directory structure.
+         // This is needed in case someone specifies a file like:
+         // /var/log/foo.bar/asd in which case we want to create
+         // /var/log/foo.bar/asd-xxxxxx.log, not
+         // /var/log/foo-xxxxxx.bar/asd
+         String parent = f.getParent();
+         String name = f.getName();
+         
+         int extIndex = name.lastIndexOf (".");
+         // Just make the code simple...
+         if (extIndex==-1)
+         {
+             extIndex=name.length();
+             name=name+".log";
+         }
+         extension = name.substring (extIndex);
+         if (extension.equals ("."))
+             extension=".log";
+         name = name.substring (0, extIndex);
+         if (parent==null)
+             this.filenameBody = new File (name).getCanonicalPath();
+         else
+             this.filenameBody = new File (parent, name).getCanonicalPath();
+     }
+ 
+     /**
+      * Creates a date format based on the given date pattern.
+      * The format determines the date strings included in the file name.
+      * @param pattern Date format string, as expected by SimpleDateFormat
+      */
+     private void createFormat (String pattern) 
+     {
+         dateFormat = new SimpleDateFormat (pattern);
+     }
+ }
Index: log4j_contrib/src/org/apache/log4j/DatedFileAppender.java
diff -c log4j_contrib/src/org/apache/log4j/DatedFileAppender.java:1.1 log4j_contrib/src/org/apache/log4j/DatedFileAppender.java:1.2
*** log4j_contrib/src/org/apache/log4j/DatedFileAppender.java:1.1	Thu Jan  3 13:48:21 2002
--- log4j_contrib/src/org/apache/log4j/DatedFileAppender.java	Fri Jan  4 14:05:02 2002
***************
*** 7,16 ****
  
  package org.apache.log4j;
  
- import java.text.SimpleDateFormat;
  import java.io.IOException;
- import java.io.File;
  import java.util.Calendar;
  
  import org.apache.log4j.helpers.LogLog;
  import org.apache.log4j.spi.LoggingEvent;
--- 7,15 ----
  
  package org.apache.log4j;
  
  import java.io.IOException;
  import java.util.Calendar;
+ import java.util.Date;
  
  import org.apache.log4j.helpers.LogLog;
  import org.apache.log4j.spi.LoggingEvent;
***************
*** 23,34 ****
   * unambiguous.
   * 
   * @author Jon Skeet <skeet@pobox.com>
   */
  public class DatedFileAppender extends FileAppender
  {
-     /** Formatter to use for dates in filename; generated from datePattern */
-     private SimpleDateFormat dateFormat;
-     
      /** Date pattern to use for formatting dates, as used in SimpleDateFormat */
      private String datePattern;
      
--- 22,31 ----
   * unambiguous.
   * 
   * @author Jon Skeet <skeet@pobox.com>
+  * @author Jim Doyle <jdoyle@iso-ne.com>
   */
  public class DatedFileAppender extends FileAppender
  {
      /** Date pattern to use for formatting dates, as used in SimpleDateFormat */
      private String datePattern;
      
***************
*** 41,52 ****
      /** When to roll the log */
      private long nextMidnight=0;
  
- 	/** Base of filename (ie everything before the extension) */
- 	protected String filenameBody=null;
- 	
-     /** File extension to add after date (includes .) */
-     protected String extension=null;
- 
      /**
       * The only supported constructor - any calls to other superconstructors 
       * could try to open the file before we necessarily have the right
--- 38,43 ----
***************
*** 59,84 ****
      }
      
      /**
!      * Overrides FileAppender.setFile(). FileAppender only
!      * ever knows about the dated filename.
       */
      public void setFile (String filename, boolean append)
          throws IOException
      {
!         String val = filename.trim();
!         String datedFilename = constructInternalFilename (val);
          super.setFile (datedFilename, append);
      }
  
      /**
!      The <b>DatePattern</b> takes a string in the same format as
!      expected by {@link SimpleDateFormat}. This options determines the
!      rollover schedule.
       */
      public void setDatePattern(String pattern) 
      {
          datePattern = pattern;
-         dateFormat = new SimpleDateFormat ("yyyyMMdd");
      }
    
      /** Returns the value of the <b>DatePattern</b> option. */
--- 50,88 ----
      }
      
      /**
!      * Closes the current opened file, and opens a new one with a dated
!      * filename, using the FileAppender superclass.
!      * Updates the <code>nextMidnight</code>
!      * time to the next time the dated filename will rollover.
!      * <p>
!      * This is called only after <code>activateOptions</code>, and is
!      * expected to actually open the file.  The filename, append flag,
!      * and date pattern options will all have been set previously.  In
!      * particular, <code>datedFile</code> will already have the date
!      * pattern option.
!      * <p>
!      * Because this overrides FileAppender.setFile(), FileAppender only
!      * ever opens files with the dated filename.
       */
      public void setFile (String filename, boolean append)
          throws IOException
      {
!         DatedFile datedFile = new DatedFile(filename, datePattern);
!         Calendar now = Calendar.getInstance();
!         String datedFilename = datedFile.getDatedFilename(now);
          super.setFile (datedFilename, append);
+         nextMidnight = datedFile.nextFilenameChange(now);
+         LogLog.debug ("Opened "+datedFilename);
+         LogLog.debug ("Next rollover: "+new Date(nextMidnight));
      }
  
      /**
!      * The <b>DatePattern</b> option takes a string in the same format as
!      * expected by {@link SimpleDateFormat}. 
       */
      public void setDatePattern(String pattern) 
      {
          datePattern = pattern;
      }
    
      /** Returns the value of the <b>DatePattern</b> option. */
***************
*** 86,152 ****
      {
          return datePattern;
      }
- 
-     /**
-      * Constructs the internal filename from a "normal" one. A filename of
-      * <code>body.ext</code> is translated to <code>body-date.ext</code>. 
-      * If there is no
-      */
-     private synchronized String constructInternalFilename (String filename)
-         throws IOException
-     {
-         File f = new File (filename);
-         
-         // Separate the name from the parent directory structure.
-         // This is needed in case someone specifies a file like:
-         // /var/log/foo.bar/asd in which case we want to create
-         // /var/log/foo.bar/asd-xxxxxx.log, not
-         // /var/log/foo-xxxxxx.bar/asd
-         String parent = f.getParent();
-         String name = f.getName();
-         
-         int extIndex = name.lastIndexOf (".");
-         // Just make the code simple...
-         if (extIndex==-1)
-         {
-             extIndex=name.length();
-             name=name+".log";
-         }
-         extension = name.substring (extIndex);
-         if (extension.equals ("."))
-             extension=".log";
-         name = name.substring (0, extIndex);
-         if (parent==null)
-             this.filenameBody = new File (name).getCanonicalPath();
-         else
-             this.filenameBody = new File (parent, name).getCanonicalPath();
- 
-         // Now get the date bit
-         return constructInternalFilename();
-     }
      
      /**
-      * Constructs the internal filename, using the current date.
-      * This also works out the next midnight.
-      */
-     private synchronized String constructInternalFilename()
-     {
-         // Work out the current midnight
-         Calendar midnight = Calendar.getInstance();
-         midnight.set(Calendar.HOUR_OF_DAY, 0);
-         midnight.set(Calendar.MINUTE, 0);
-         
-         String dateString = dateFormat.format (midnight.getTime());
-         
-         // Work out the next midnight
-         midnight.add (Calendar.HOUR, 24);
-         nextMidnight = midnight.getTime().getTime();
-         
-         return filenameBody+"-"+dateString+extension;
-     }
- 
-     /**
       * Overrides FileAppender.subAppend() to check for rollover.
       */
      protected synchronized void subAppend (LoggingEvent event)
      {
--- 90,105 ----
      {
          return datePattern;
      }
      
      /**
       * Overrides FileAppender.subAppend() to check for rollover.
+      * Will re-open the file with a new dated filename if rollover is
+      * necessary.
+      * <p>
+      * Assumes all options have been set, so we can open a correct
+      * dated filename.  If <code>setFile</code> has not yet been called,
+      * then we will call it now, because the <code>nextMidnight</code>
+      * value will be zero and cause a rollover.
       */
      protected synchronized void subAppend (LoggingEvent event)
      {
***************
*** 155,180 ****
          {
              try
              {
!                 rollover();
              }
              catch (IOException e)
              {
!                 LogLog.error("rollover() failed.", e);
              }
          }
          super.subAppend (event);
-     }
-     
-     /**
-      * Rolls over, closing the current log (if there is one),
-      * working out the new filename, opening the file, and replaying
-      * any startup events.
-      */
-     private synchronized void rollover()
-         throws IOException
-     {
-         String datedFilename = constructInternalFilename();
-         super.setFile (datedFilename, fileAppend);
-         LogLog.debug ("Rolled log to "+datedFilename);
      }
  }
--- 108,120 ----
          {
              try
              {
!                 setFile(fileName, fileAppend);
              }
              catch (IOException e)
              {
!                 LogLog.error("setFile() failed.", e);
              }
          }
          super.subAppend (event);
      }
  }
Index: log4j_contrib/src/org/apache/log4j/DatedFileTest.java
diff -c /dev/null log4j_contrib/src/org/apache/log4j/DatedFileTest.java:1.1
*** /dev/null	Fri Jan  4 14:17:20 2002
--- log4j_contrib/src/org/apache/log4j/DatedFileTest.java	Fri Jan  4 14:05:02 2002
***************
*** 0 ****
--- 1,179 ----
+ /*
+  * Copyright (C) The Apache Software Foundation. All rights reserved.
+  *
+  * This software is published under the terms of the Apache Software
+  * License version 1.1, a copy of which has been included with this
+  * distribution in the LICENSE.APL file.  */
+ 
+ package org.apache.log4j;
+ 
+ import java.io.File;
+ import java.util.Calendar;
+ import java.util.Date;
+ import junit.framework.TestCase;
+ 
+ /**
+  * JUnit test cases for DatedFile.
+  * @author Jim Doyle <jdoyle@iso-ne.com>
+  */
+ public class DatedFileTest extends TestCase
+ {
+     /** Date shared by tests: June 1, 2001 */
+     private Calendar june1_2001;
+ 
+     /** Date shared by tests: June 2, 2001 */
+     private Calendar june2_2001;
+ 
+    
+     /**
+      * Constructs a test case that will run the named test method
+      * @param name Name of test method
+      */
+     public DatedFileTest (String name)
+     {
+         super(name);
+     }
+     
+     /**
+      * Should create correct filename for
+      * a normal case like /var/log/foo/bar/asd.log.
+      */
+     public void testRegularName () throws Exception
+     {
+         String expectedPath =
+             new File("/var/log/foo/bar/asd-20010601.log").getCanonicalPath();
+         DatedFile file = new DatedFile("/var/log/foo/bar/asd.log", "yyyyMMdd");
+         assertEquals(expectedPath, file.getDatedFilename(june1_2001));
+     }
+     
+     /**
+      * Should still insert date at end when there's a dot in
+      * one of the directories, like /var/log/foo.bar/asd.log.
+      */
+     public void testDotInDirectory () throws Exception
+     {
+         String expectedPath =
+             new File("/var/log/foo.bar/asd-20010601.log").getCanonicalPath();
+         DatedFile file = new DatedFile("/var/log/foo.bar/asd.log", "yyyyMMdd");
+         assertEquals(expectedPath, file.getDatedFilename(june1_2001));
+     }
+     
+     /**
+      * Should tack on ".log" as extension when there is no extension.
+      */
+     public void testExtensionForced () throws Exception
+     {
+         String expectedPath =
+             new File("/var/log/foo/bar/asd-20010601.log").getCanonicalPath();
+         DatedFile file = new DatedFile("/var/log/foo/bar/asd", "yyyyMMdd");
+         assertEquals(expectedPath, file.getDatedFilename(june1_2001));
+     }
+     
+     /**
+      * Should handle different date patterns
+      */
+     public void testDifferentDatePattern () throws Exception
+     {
+         String expectedPath =
+             new File("/var/log/foo/bar/asd-06012001.log").getCanonicalPath();
+         DatedFile file = new DatedFile("/var/log/foo/bar/asd.log", "MMddyyyy");
+         assertEquals(expectedPath, file.getDatedFilename(june1_2001));
+     }
+     
+     /**
+      * Should get date even when time is in middle of day
+      */
+     public void testDateFromMiddleOfDay () throws Exception
+     {
+         june1_2001.add(Calendar.HOUR_OF_DAY, -12);
+         String expectedPath =
+             new File("/var/log/foo/bar/asd-20010531.log").getCanonicalPath();
+         DatedFile file = new DatedFile("/var/log/foo/bar/asd.log", "yyyyMMdd");
+         assertEquals(expectedPath, file.getDatedFilename(june1_2001));
+     }
+     
+     /**
+      * Should get next rollover date when time is exactly at midnight.
+      */
+     public void testRolloverAtMidnight () throws Exception
+     {
+         DatedFile file = new DatedFile("/var/log/foo/bar/asd.log", "yyyyMMdd");
+         assertEquals(june2_2001.getTime(),
+                      new Date(file.nextFilenameChange(june1_2001)));
+     }
+     
+     /**
+      * Should get next rollover date when time is a few seconds past midnight.
+      */
+     public void testRolloverFewSecondsLate () throws Exception
+     {
+         june1_2001.add(Calendar.SECOND, 10);
+         DatedFile file = new DatedFile("/var/log/foo/bar/asd.log", "yyyyMMdd");
+         assertEquals(june2_2001.getTime(),
+                      new Date(file.nextFilenameChange(june1_2001)));
+     }
+ 
+     /**
+      * Should get next rollover date when time is hours after midnight.
+      */
+     public void testRolloverHoursLate () throws Exception
+     {
+         june1_2001.add(Calendar.HOUR_OF_DAY, 12);
+         june1_2001.add(Calendar.SECOND, 10);
+         DatedFile file = new DatedFile("/var/log/foo/bar/asd.log", "yyyyMMdd");
+         assertEquals(june2_2001.getTime(),
+                      new Date(file.nextFilenameChange(june1_2001)));
+     }
+ 
+     /**
+      * Should get next rollover date on short (EST-EDT) day.
+      */
+     public void testRolloverEstEdt () throws Exception
+     {
+         Calendar apr1_2001 = Calendar.getInstance();
+         apr1_2001.clear();
+         apr1_2001.set(2001, Calendar.APRIL, 1);
+         apr1_2001.add(Calendar.MINUTE, 30);
+         
+         Calendar apr2_2001 = Calendar.getInstance();
+         apr2_2001.clear();
+         apr2_2001.set(2001, Calendar.APRIL, 2);
+ 
+         DatedFile file = new DatedFile("/var/log/foo/bar/asd.log", "yyyyMMdd");
+         assertEquals(apr2_2001.getTime(),
+                      new Date(file.nextFilenameChange(apr1_2001)));
+     }
+ 
+     /**
+      * Should get next rollover date on long (EDT-EST) day.
+      */
+     public void testRolloverEdtEst () throws Exception
+     {
+         Calendar oct28_2001 = Calendar.getInstance();
+         oct28_2001.clear();
+         oct28_2001.set(2001, Calendar.OCTOBER, 28);
+         oct28_2001.add(Calendar.MINUTE, 30);
+         
+         Calendar oct29_2001 = Calendar.getInstance();
+         oct29_2001.clear();
+         oct29_2001.set(2001, Calendar.OCTOBER, 29);
+ 
+         DatedFile file = new DatedFile("/var/log/foo/bar/asd.log", "yyyyMMdd");
+         assertEquals(oct29_2001.getTime(),
+                      new Date(file.nextFilenameChange(oct28_2001)));
+     }
+ 
+     /**
+      * Sets up test dates
+      */
+     protected void setUp ()
+     {
+         june1_2001 = Calendar.getInstance();
+         june1_2001.clear();
+         june1_2001.set(2001, Calendar.JUNE, 1);
+ 
+         june2_2001 = Calendar.getInstance();
+         june2_2001.clear();
+         june2_2001.set(2001, Calendar.JUNE, 2);
+     }
+ }
