/* JSDate
** Copyright (c) 1999 Christopher D. Doemel
**
** This script is part of ZDU's "JavaScript Cookbook" class.  The
** in-depth explanation on how to use this script is presented in
** the class lessons.  See <http://www.zdu.com/> for information
** about the JavaScript Cookbook and other ZDU classes.
**
** Permission to use and modify this script is granted, so long as
** the above copyright is maintained, any modifications are
** documented, and credit is given for use of the script.
**
** Version 1.1.1, August 1999
**
** 1.0.0   Initial release (25 Jan 1999)
** 1.0.1   Fixed stupid error in if statements (27 Jan 1999).
** 1.0.2   Fixed two additional errors:
**         * displayFormattedDate() was not receiving the format
**           as a string; thus, the string methods were not working.
**         * The substring() method should have been used instead
**           of substr() in displayFormattedDate() for compatibility
**           with earlier browsers.
**         Thanks to Larry Coats for his assistance in finding (and
**         squashing) these bugs.
** 1.0.3   Fixed one error with substring() method that caused two-digit
**         years to be returned as a single digit.  Thanks to Richard
**         Bunyan for finding (and squashing) this bug.
** 1.0.4   An unquoted "a" or "A" not part of one of the meridiem codes
**         caused an overflow error (the script kept calling itself
**         because the code never dealt with the "a" or "A".  Fixed.
**         (14 Feb 1999)
** 1.1.0   Many, many changes and two additional features (which
**         warrants the version 1.1, I think).
**         - Script header renamed from "JSTime" to "JSDate" to match
**           the filename.
**         - Functions renamed from "displayXXX" to "getXXX", e.g.,
**           displayFormattedTime() is now getFormattedTime().  The
**           name seems more appropriate, since the functions do not
**           actually display the time, they return a formatted time.
**           Apologies in advance for those who are already using the
**           library.
**         - Much more commenting in the code.
**         - Added seconds (s) to the format codes.  Why didn't I do
**           this in the first place...?
**         - New format code for dates: dth.  Yes, it's weird, but it
**           returns the date followed by an ordinal (e.g., 1st, 2nd,
**           etc.).
**         - Added internationalization information in commenting
**           around utility functions.
**         (04 Apr 1999)
** 1.1.1   Fixed bug with dth code that didn't properly display
**         ordinals for days greater than 10. (15 Aug 1999)
*/

/* The getFormattedTime() function takes a single argument: a string
** that specifies a date/time format using the codes described herein.
** This is the real workhorse of the JSDate script.
**
** You can use the getFormattedTime() function anywhere a string can be
** used in JavaScript.  For example:
**
**    document.write(getFormattedTime("h:mm am/pm");
**
** This would write the current time (e.g., "5:14 pm") in your document.
** Also useful for updating clocks:
**
**    document.myForm.myClock.value = getFormattedTime("h:mm am/pm");
**
** The format can use one or more of the following codes:
**
** Month (M)
** Format Code   Returns the month as
** -----------   ----------------------------------------------------------
** M             A number without a leading 0 (zero) for single-digit
**               months. For example, July is "7".
** MM            A number with a leading 0 (zero) for single-digit months.
**               For example, July is "07".
** MMM           A three-letter abbreviation.  For example, July is "Jul".
** MMMM          The full name of the month.
**
** Day (d)
** Format Code   Returns the day as
** -----------   ----------------------------------------------------------
** d             A number without a leading 0 (zero) for single-digit days.
** dth           A number with a trailing ordinal (e.g., 1st, 2nd, etc.).
**               Yes, it is indeed an awkward format code.
** dd            A number with a leading 0 (zero) for single-digit days.
**               For example, the sixth day of the month is displayed as
**               "06".
** ddd           A three-letter abbreviation. For example, Tuesday is
**               displayed as "Tue".
** dddd          The full name of the day of the week.
**
** Year (y)
** Format Code   Returns the year as
** -----------   ----------------------------------------------------------
** yy            Two digits with a leading 0 (zero) for years 01 through
**               09. For example, 1995 is displayed as "95", and 2006 is
**               displayed as "06".
** yyyy          Four digits.
**
** Hours (h)
** A lower-case "h" bases time on the 12-hour clock.  An uppercase "H" bases
** time on the 24-hour, or military, clock; for example, 5 P.M. is displayed
** as "17".
**
** Format Code   Returns the hour
** -----------   ----------------------------------------------------------
** h or H        Without a leading 0 (zero) for single-digit hours. For
**               example, the hour of 9 A.M. is displayed as "9".
** hh or HH      With a leading 0 (zero) for single-digit hours. For
**               example, the hour of 9 A.M. is displayed as "09".
**
** Minutes (m)
** Format Code   Returns minutes
** -----------   ----------------------------------------------------------
** m             Without a leading 0 for single-digit minutes. For example,
**               "m" displays "2".
** mm            With a leading 0 (zero) for single-digit minutes. For
**               example, "mm" displays "02".
**
** Seconds (s)
** Format Code   Returns seconds
** -----------   ----------------------------------------------------------
** s             Without a leading 0 for single-digit seconds. For example,
**               "s" displays "2".
** ss            With a leading 0 (zero) for single-digit seconds. For
**               example, "ss" displays "02".
**
** Meridiem
** Format Code   Returns
** -----------   ----------------------------------------------------------
** AM/PM		 Upper-case. For example, "h AM/PM" displays "9 AM" or
**               "5 PM".
** am/pm         Lower-case. For example, "h am/pm" displays "9 am" or
**               "5 pm".
** A/P           Abbreviated upper-case. For example, "h A/P" displays
**               "9 A" or "5 P".
** a/p           Abbreviated lower-case. For example, "h a/p" displays
**               "9 a" or "5 p".
**
** If you want to include literal text, enclose it in single quotation
** marks -- otherwise it will be interpreted as a date code.  For instance,
** "'Today is' MMMM d." displays "Today is March 16."
**
** Punctuation marks are OK.  To display the time, for instance, you
** can use "h:mm AM/PM" which would display "8:03 PM".
*/

function getFormattedDate(theFormat) {
    var i = 0;
    var lastChar = "";
    var fieldCode = "";
    var fieldResult = "";
    var theDate = new Date();
    
    /* Convert theFormat to a string, even if it is already a string.
    ** If I don't do this, a lot of the String methods don't work.
    */
    theFormat = new String(theFormat);
    
    // Handle meridiem codes -- you know, AM or PM stuff.
    if (theFormat.charAt(i) == "a" || theFormat.charAt(i) == "A") {
        if (theFormat.indexOf("am/pm") == 0) {
            fieldResult = lcMeridiem(theDate);
            i += 5;
        }
        else if (theFormat.indexOf("AM/PM") == 0) {
            fieldResult = ucMeridiem(theDate);
            i += 5;
        }
        else if (theFormat.indexOf("a/p") == 0) {
            fieldResult = lcAbbrMeridiem(theDate);
            i += 3;
        }
        else if (theFormat.indexOf("A/P") == 0) {
            fieldResult = ucAbbrMeridiem(theDate);
            i += 3;
        }
        else {
            fieldResult = theFormat.charAt(i);
            i++;
        }
    }
    // Handle the oddball ordinal date code
    else if (theFormat.indexOf("dth") == 0) {
       fieldResult = getOrdinalDate(theDate);
       i += 3;
    }
    // Handle literal text (text enclosed by single quotes).
    else if (theFormat.charAt(i) == "'") {
        while (++i <= theFormat.length && theFormat.charAt(i) != "'") {
            fieldResult += theFormat.charAt(i);
        }
        if (theFormat.charAt(i) == "'") { ++i };
    }
    else {
        // Collect the next field code by collecting repeating characters.
        while (i <= theFormat.length) {
            if (lastChar == "") {
                // First time through the loop; grab a char!
                lastChar = theFormat.charAt(i);
                fieldCode += lastChar;
                i++;
                continue;
            }
            if (lastChar == theFormat.charAt(i)) {
                // The char is the same; save it and move on
                fieldCode += lastChar;
                i++;
                continue;
            }
            if (lastChar != theFormat.charAt(i)) {
                // The char is different; time to parse the field!
                break;
            }
        }
        
        // Handle month formats
        if      (fieldCode == "M")    { fieldResult = getOneDigitMonth(theDate);  }
        else if (fieldCode == "MM")   { fieldResult = getTwoDigitMonth(theDate);  }
        else if (fieldCode == "MMM")  { fieldResult = getShortMonthName(theDate); }
        else if (fieldCode == "MMMM") { fieldResult = getLongMonthName(theDate);  }
        
        // Handle date formats
        else if (fieldCode == "d")    { fieldResult = getOneDigitDate(theDate); }
        else if (fieldCode == "dd")   { fieldResult = getTwoDigitDate(theDate); }
        else if (fieldCode == "ddd")  { fieldResult = getShortDayName(theDate); }
        else if (fieldCode == "dddd") { fieldResult = getLongDayName(theDate);  }
        
        // Handle year formats
        else if (fieldCode == "yy")   { fieldResult = getTwoDigitYear(theDate);  }
        else if (fieldCode == "yyyy") { fieldResult = getFourDigitYear(theDate); }
        
        // Handle hour formats
        else if (fieldCode == "h")    { fieldResult = getOneDigitHour(theDate);         }
        else if (fieldCode == "H")    { fieldResult = getOneDigitMilitaryHour(theDate); }
        else if (fieldCode == "hh")   { fieldResult = getTwoDigitHour(theDate);         }
        else if (fieldCode == "HH")   { fieldResult = getTwoDigitMilitaryHour(theDate); }
        
        // Handle minute formats
        else if (fieldCode == "m")    { fieldResult = getOneDigitMinutes(theDate); }
        else if (fieldCode == "mm")   { fieldResult = getTwoDigitMinutes(theDate); }
        
        // Handle second formats
        else if (fieldCode == "s")    { fieldResult = getOneDigitSeconds(theDate); }
        else if (fieldCode == "ss")   { fieldResult = getTwoDigitSeconds(theDate); }
        
        // Handle everything else
        else                          { fieldResult = fieldCode; };
    }
    
    // If we're not done parsing the format codes, call getFormattedDate() with the
    // remaining codes.
    if (i == theFormat.length) { return fieldResult; }
    else { return fieldResult + getFormattedDate(theFormat.substring(i,theFormat.length)); }
}

/* The getDate() and getTime() functions call getFormattedDate() with default
** formats -- this saves time when you just want a standard date or time.
*/

// Returns date, e.g. "December 9, 2001"
function getDate() { return getFormattedDate("MMMM d, yyyy"); }

// Returns time, e.g., "6:03 pm"
function getTime() { return getFormattedDate("h:mm am/pm"); }

/* The following routines construct various portions of the final
** date, and are typically called by the getFormattedDate()
** function -- but you could use them yourself by providing a
** date.  To get the current two-digit date, for example:
**
**    getTwoDigitDate(new Date());
**
** The current version of the script is hard-coded for English
** month and day names.  If you want to use a different language,
** modify the getShortMonthName(), getLongMonthName(),
** getShortDayName(), and getLongDayName(), and getOrdinalDate(),
** functions to use the appropriate month and day names.
*/

function getOneDigitMonth(theDate) {
    return theDate.getMonth() + 1;
}

function getTwoDigitMonth(theDate) {
    var theMonth = getOneDigitMonth(theDate);
    return (theMonth < 10 ? "0" + theMonth : theMonth);
}

function getShortMonthName(theDate) {
    var theMonth = getOneDigitMonth(theDate);
    if      (theMonth == 1)  { return "Jan"; }
    else if (theMonth == 2)  { return "Feb"; }
    else if (theMonth == 3)  { return "Mar"; }
    else if (theMonth == 4)  { return "Apr"; }
    else if (theMonth == 5)  { return "May"; }
    else if (theMonth == 6)  { return "Jun"; }
    else if (theMonth == 7)  { return "Jul"; }
    else if (theMonth == 8)  { return "Aug"; }
    else if (theMonth == 9)  { return "Sep"; }
    else if (theMonth == 10) { return "Oct"; }
    else if (theMonth == 11) { return "Nov"; }
    else                     { return "Dec"; }
}

function getLongMonthName(theDate) {
    var theMonth = getOneDigitMonth(theDate);
    if      (theMonth == 1)  { return "January";   }
    else if (theMonth == 2)  { return "February";  }
    else if (theMonth == 3)  { return "March";     }
    else if (theMonth == 4)  { return "April";     }
    else if (theMonth == 5)  { return "May";       }
    else if (theMonth == 6)  { return "June";      }
    else if (theMonth == 7)  { return "July";      }
    else if (theMonth == 8)  { return "August";    }
    else if (theMonth == 9)  { return "September"; }
    else if (theMonth == 10) { return "October";   }
    else if (theMonth == 11) { return "November";  }
    else                     { return "December";  }
}

function getOneDigitDate(theDate) {
    return theDate.getDate();
}

function getOrdinalDate(theDate) {
    var theDay = getOneDigitDate(theDate);
    if ((theDay == 1) || (theDay == 21) || (theDay == 31)) {
        return theDay + "st";
    }
    else if ((theDay == 2) || (theDay == 22)) {
        return theDay + "nd";
    }
    else if ((theDay == 3) || (theDay == 23)) {
        return theDay + "rd";
    }
    else { return theDay + "th"; }
}

function getTwoDigitDate(theDate) {
    var theDay = getOneDigitDate(theDate);
    return (theDay < 10 ? "0" + theDay : theDay);
}

function getShortDayName(theDate) {
    var theDay = theDate.getDay();
    if      (theDay == 0) { return "Sun"; }
    else if (theDay == 1) { return "Mon"; }
    else if (theDay == 2) { return "Tue"; }
    else if (theDay == 3) { return "Wed"; }
    else if (theDay == 4) { return "Thu"; }
    else if (theDay == 5) { return "Fri"; }
    else                 { return "Sat"; }
}

function getLongDayName(theDate) {
    var theDay = theDate.getDay();
    if      (theDay == 0) { return "Sunday";    }
    else if (theDay == 1) { return "Monday";    }
    else if (theDay == 2) { return "Tuesday";   }
    else if (theDay == 3) { return "Wednesday"; }
    else if (theDay == 4) { return "Thursday";  }
    else if (theDay == 5) { return "Friday";    }
    else                 { return "Saturday";  }
}

function getFourDigitYear(theDate) {
    var theYear = theDate.getYear();
    if (theYear < 1000) { theYear += 1900; }
    return theYear;
}

function getTwoDigitYear(theDate) {
    var theYear = getFourDigitYear(theDate);
    var strYear = new String(theYear);
    return strYear.substring(2,4);
}

function getOneDigitHour(theDate) {
    var theHour = theDate.getHours() % 12;
    if (theHour == 0) { theHour = 12; }
    return theHour;
}

function getTwoDigitHour(theDate) {
    var theHour = getOneDigitHour(theDate);
    return (theHour < 10 ? "0" + theHour : theHour);
}

function getOneDigitMilitaryHour(theDate) {
    return theDate.getHours();
}

function getTwoDigitMilitaryHour(theDate) {
    var theHour = getOneDigitMilitaryHour(theDate);
    return (theHour < 10 ? "0" + theHour : theHour);
}

function getOneDigitMinutes(theDate) {
    return theDate.getMinutes();
}

function getTwoDigitMinutes(theDate) {
    var theMinutes = getOneDigitMinutes(theDate);
    return (theMinutes < 10 ? "0" + theMinutes : theMinutes);
}

function getOneDigitSeconds(theDate) {
    return theDate.getSeconds();
}

function getTwoDigitSeconds(theDate) {
    var theSeconds = getOneDigitSeconds(theDate);
    return (theSeconds < 10 ? "0" + theSeconds : theSeconds);
}

function lcMeridiem(theDate) {
    var theHour = getOneDigitMilitaryHour(theDate);
    return (theHour < 12 ? "am" : "pm");
}

function lcAbbrMeridiem(theDate) {
    var theHour = getOneDigitMilitaryHour(theDate);
    return (theHour < 12 ? "a" : "p");
}

function ucMeridiem(theDate) {
    var theHour = getOneDigitMilitaryHour(theDate);
    return (theHour < 12 ? "AM" : "PM");
}

function ucAbbrMeridiem(theDate) {
    var theHour = getOneDigitMilitaryHour(theDate);
    return (theHour < 12 ? "A" : "P");
}