/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#ifndef _H_MvDATE_
#define _H_MvDATE_

#include "mars.h"
#include "inc_iostream.h"
#include <string>
using std::string;

//! Class to handle dates and times
/*! This class stores the date as a Julian day (where the reference day #0
 *  is Monday 01 January 4713 BCE). The time part is stored as seconds since
 *  midnight.
 *  The class contains methods, among others, to compare or increase or decrease
 *  dates, extract different date and time components, and format for printing.\n \n
 *  \b Customisable \n
 *  Non-numeric date information (short and full month names, short and full
 *  weekday names), as well as the default date formats, are user customisable
 *  through Metview Preferences menu item.
 *  This makes it very easy for non-English users to translate these texts
 *  into their own language and to customise the default date format to their
 *  own date writing standard (or for USA users to use their weird notation
 *  of swapped month and day ;-).\n
 *  For the time part, only formats for 24 hour clock have been implemented,
 *  i.e. it is not possible to customise hours to be formatted like "6pm" or "10AM".\n \n
 *  \b Reference: \n
 *  Collected Algorithms from CACM.\n
 *  Algorithm #199 - "Conversions between Calendar Date and Julian Day Number"
 */

class MvDate {
	//! Friend function to write MvDate objects
	/*! The format is taken from user's Metview Preferences.
	 *  The default in Metview is ISO 8601 standard date and time format.
	 */
	friend ostream& operator<< ( ostream& aStream, const MvDate& dd );

static const char *stringFormat;
static const char *numberFormat;

	long julian;
	long second;

	//! Internal comparision function
	int compare(const MvDate& d) const
		{ return julian!=d.julian?julian-d.julian:second-d.second;};
public:
	//! Constructor; by default sets today's date at 00UTC
	/*! Negative values can be used to indicate a relative date.
	 *  By default relative days are relative to today, unless
	 *  environment variable \c MARS_REFERENCE_DATE has been set.\n \n
	 *  The desimal part is interpreted as a fraction of a day (of 24 hours),
	 *  thus 0.25 indicates 06 utc and 0.875 indicates 21 utc.\n \n
	 *  NOTE that there is no validity test for the syntax and
	 *  some clearly invalid values are converted to valid dates,
	 *  for example \c 20081535 becomes 2009-04-06!
	 */
	MvDate(double n = 0);

	//! Copy constructor
	MvDate(const MvDate& d) { julian = d.julian; second = d.second;   };

	//! Constructor from a string
	/*! If \c dd has a valid date syntax, the constructor
	 *  parses \c dd into the internal presentation.
	 *  Otherwise first converts \c dd into an integer which
	 *  is then interpreted as a date.\n \n
	 *  NOTE that there is no validity test for the syntax and
	 *  some clearly invalid values are converted to valid dates,
	 *  for example "2008-15-35" becomes 2009-04-06!
	 */
	MvDate(const char* dd);

	//! Assignment operator
	MvDate& operator=(const MvDate& d) { julian = d.julian; second = d.second; return *this; }

	//! Returns the internal Julian day number (days since 01 January 4713 BCE)
	long _julian() const { return julian; };

	//! Returns the internal time of the day, in seconds since midnight
	long _second() const { return second; };

	//! Decreases the date/time
	/*! The integer part of \c ddtt represents days and the decimal part
	 *  is for hours and minutes (as a fraction of 24 hours). Thus the value
	 *  of \c 1.75 as an operand would "subtract" one day and 18 hours
	 *  from the operand MvDate object.\n \n
	 *  Example:\n
	 *  <PRE>
	 *     MvDate d;            //-- today at 00 utc
	 *     MvDate x = d - 1.75; //-- day before yesterday at 06 utc
	 *  </PRE>
	 */
	MvDate   operator-(double ddtt)const ;

	//! Increases the date/time
	/*! The integer part of \c ddtt represents days and the decimal part
	 *  is for hours and minutes (as a fraction of a day). Thus the value
	 *  of \c 1.25 as an argument would "add" one day and 6 hours
	 *  to the operand MvDate object.
	 */
	MvDate   operator+(double ddtt)const ;

	//! Increases the current date/time
	/*! Example:\n
	 *  <PRE>
	 *     MvDate d(0);   //-- today at 00 utc
	 *     d += 1.125;    //-- tomorrow at 03 utc
	 *  </PRE>
	 */
	MvDate&  operator+=(double ddtt);

	//! Decreases the current date/time
	MvDate&  operator-=(double ddtt);

	//! Returns the time difference between two MvDate objects
	/*! The integer part of the returned value represents days
	 *  and the decimal part represents the fraction of a day
	 */
	double operator-(const MvDate& )const ;

	//! 'Greater-than' comparison operator
	bool  operator>(const MvDate& d)const   { return compare(d) >  0; };

	//! 'Less-than' comparison operator
	bool  operator<(const MvDate& d)const   { return compare(d) <  0; };

	//! 'Greater-or-equal' comparison operator
	bool  operator>=(const MvDate& d)const  { return compare(d) >= 0; };

	//! 'Less-or-equal' comparison operator
	bool  operator<=(const MvDate& d)const  { return compare(d) <= 0; };

	//! 'Equal' comparison operator
	bool  operator==(const MvDate& d)const  { return compare(d) == 0; };

	//! 'Non-equal' comparison operator
	bool  operator!=(const MvDate& d)const  { return compare(d) != 0; };

	//! "C" style print method for standard output
	/*! Use "C++" style friend operator<<() for chaining several output
	 *  objects or for writing to any stream
	 */
	void Print() const;

	//! Returns the date part as an integer YYMMDD
	/*! WARNING: two digit year!
	 */
	int  YyMmDd(void) const         { return julian_to_date(julian,false); }

	//! Returns the date part as an integer YYYYMMDD
	int  YyyyMmDd(void)  const      { return julian_to_date(julian,true); }

	//! Returns the date part as an integer YYYYddd
	/*! Digits \c ddd represent the running day (Julian) of the year
	 */
	int  yyyyddd(void)  const ;

	//! Returns the year part of the date
	int  Year(void)  const     { return YyyyMmDd() / 10000;       };

	//! Returns the month part of the date
	int  Month(void)  const    { return (YyyyMmDd() / 100) % 100; };

	//! Returns the day part of the date
	int  Day(void)   const     { return YyyyMmDd() % 100;         };

	//! Returns the hour part of the time
	int  Hour(void)  const     { return second / 3600;            };

	//! Returns the minutes part of the time
	int  Minute(void)  const   { return (second / 60) %60;        };

	//! Returns the seconds part of the time
	int  Second(void)  const   { return second % 60;              };

	//! Returns the time as an integer HHMMSS
	int  hhmmss()        const;

	//! Returns the Julian day within the year of MvDate
	int  Julian(void) const;

	//! Returns the day of the week: 1=Monday, 7=Sunday
	int  DayOfWeek(void) const { return julian % 7 + 1;           };

	//! Creates a custom formatted date/time text string
	/*! Argument \c fmt defines the format and \c buf is the target character array.
	 *  Recognised format letter combinations are:
	 *  <PRE>
	 *     yy   - year with 2 digits
	 *     yyyy - year with 4 digits
	 *     m    - month with minimum digits
	 *     mm   - month with 2 digits
	 *     mmm  - short month name (user customisable)
	 *     mmmm - full month name (user customisable)
	 *     d    - day of the month with minimum digits
	 *     dd   - day of the month with 2 digits
	 *     ddd  - short weekday name (user customisable)
	 *     dddd - full weekday name (user customisable)
	 *     D    - Julian day with minimum digits
	 *     DDD  - Julian day with 3 digits
	 *     H    - hour with minimum digits (24 hour clock)
	 *     HH   - hour with 2 digits (24 hour clock)
	 *     M    - minutes with minimum digits
	 *     MM   - minutes with 2 digits
	 *     S    - seconds with minimum digits
	 *     SS   - seconds with 2 digits
	 *  </PRE> All other characters are used as is. Users can customise
	 *  month and weekday names through Metview User Interface.\n
	 *  Here are some (non-customised) examples:
	 *  <PRE>
	 *    MvDate d(20080105.375);
	 *    std::cout << d << std::endl;   //-- 2008-01-05 09:00:00
	 *
	 *    char buf[100];
	 *    d.Format("d mmm yyyy at H:M", buf);
	 *    std::cout << buf << std::endl; //-- 5 Jan 2008 at 9:0
	 *
	 *    d.Format("d/m/yy - yymmdd:HH", buf);
	 *    std::cout << buf << std::endl; //-- 5/1/08 - 080105:09
	 *
	 *    d.Format("dd/mm/yyyy @ HH:MM", buf);
	 *    std::cout << buf << std::endl; //-- 05/01/2008 @ 09:00
	 *
	 *    d.Format("dddd d mmmm yyyy", buf);
	 *    std::cout << buf << std::endl; //-- Saturday 5 January 2008
	 *  </PRE>
	 */
	void Format(const char* fmt, char* buf) const;

	//! Returns the number of days of the month in the current object
	int  daysInMonth() const;

	//! Subtracts date \c dd from the current object
	/*! The returned value is an integer of the form \c mmddHH
	 *  where \c mm is for months, \c dd for days, and \c HH for hours.
	 *  Thus returned value \c 1506 indicates a difference of \c 0
	 *  months, \c 15 days and \c 6 hours.
	 */
	long   magicsSub(const MvDate& dd) const;

	//! Advances the current object date/time
	/*! The argument \c mmddHH is an integer, as described
	 *  for the return value from method magicsSub()
	 */
	MvDate magicsAdd(long mmddHH) const;

	//! Returns the date formatted as "yyyy-mm-dd HH:MM", suitable for Magics 6
	/*! See Format()
	 */
        string magicsDate() const;

	//! Returns the date formatted as "yyyymmdd.f"
	/*! Where \c .f is a fraction of a day (of 24 hours)
	 */
	double YyyyMmDd_r() const;

	//! Returns the time difference between \c dd and the current object, in days
	/*! The decimal part will represent a fraction of a day (of 24 hours),
	 *  i.e. 3.25 would represent 3 days and 6 hours
	 */
	double time_interval_days( const MvDate& dd ) const;

	//! Returns the time difference between \c dd and the current object, in hours
	/*! The decimal part will represent a fraction of an hour (of 60 minutes)
	 *  i.e. 3.25 would represent 3 hours and 15 minutes
	 */
	double time_interval_hours( const MvDate& dd ) const;

	//! Returns the time difference between \c dd and the current object, in minutes
	/*! The decimal part will represent a fraction of a minute (of 60 seconds)
	 *  i.e. 3.2 would represent 3 minutes and 12 seconds
	 */
	double time_interval_mins( const MvDate& dd ) const;

	//! Returns the default format string for text output
	/*! The default format string is taken from user's Preferences
	 *  and thus different users can have different defaults.
	 */
	static const char *StringFormat() ;

	//! Returns the default format string for numeric output
	/*! The default format string is taken from user's Preferences
	 *  and thus different users can have different defaults.
	 */
	static const char *NumberFormat();

	//! Returns the name of the month \c mm (\c mm = 1...12)
	/*! If \c full is \c true, then the full name is returned,
	 *  otherwise the short name is returned.
	 *  The name is taken from user's Preferences
	 *  and thus different users can have different defaults.
	 */
	static const char *MonthName(int mm, boolean full);

	//! Returns the name of the day \c dd (\c dd = 1...7)
	/*! If \c full is \c true, then the full name is returned,
	 *  otherwise the short name is returned.
	 *  The name is taken from user's Preferences
	 *  and thus different users can have different defaults.
	 */
	static const char *DayName(int dd, boolean full);

};
#endif
