// swad_date.c: dates /* SWAD (Shared Workspace At a Distance), is a web platform developed at the University of Granada (Spain), and used to support university teaching. This file is part of SWAD core. Copyright (C) 1999-2019 Antonio Caņas Vargas This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ /*****************************************************************************/ /********************************* Headers ***********************************/ /*****************************************************************************/ #define _GNU_SOURCE // For vasprintf #include // For vasprintf #include // For free #include // For string functions #include // For time functions (mktime...) #include "swad_box.h" #include "swad_calendar.h" #include "swad_config.h" #include "swad_database.h" #include "swad_date.h" #include "swad_form.h" #include "swad_global.h" #include "swad_HTML.h" #include "swad_parameter.h" #include "swad_setting.h" /*****************************************************************************/ /************** External global variables from others modules ****************/ /*****************************************************************************/ extern struct Globals Gbl; /*****************************************************************************/ /***************************** Public constants ******************************/ /*****************************************************************************/ /*****************************************************************************/ /**************************** Private constants ******************************/ /*****************************************************************************/ const unsigned Dat_NumDaysMonth[1 + 12] = { 0, 31, // 1: January 28, // 2: February 31, // 3: Mars 30, // 4: April 31, // 5: May 30, // 6: June 31, // 7: July 31, // 8: Agoust 30, // 9: September 31, // 10: October 30, // 11: November 31, // 12: December }; const char *Dat_TimeStatusClassVisible[Dat_NUM_TIME_STATUS] = { "DATE_RED", // Dat_PAST "DATE_GREEN", // Dat_PRESENT "DATE_BLUE", // Dat_FUTURE }; const char *Dat_TimeStatusClassHidden[Dat_NUM_TIME_STATUS] = { "DATE_RED_LIGHT", // Dat_PAST "DATE_GREEN_LIGHT", // Dat_PRESENT "DATE_BLUE_LIGHT", // Dat_FUTURE }; /*****************************************************************************/ /****************************** Internal types *******************************/ /*****************************************************************************/ /*****************************************************************************/ /**************************** Private prototypes *****************************/ /*****************************************************************************/ static void Dat_PutIconsDateFormat (void); static unsigned Dat_GetParamDateFormat (void); /*****************************************************************************/ /************** Put icons to select the first day of the week ****************/ /*****************************************************************************/ void Dat_PutBoxToSelectDateFormat (void) { extern const char *Hlp_PROFILE_Settings_dates; extern const char *Txt_Dates; Dat_Format_t Format; /***** Begin box *****/ Box_BoxBegin (NULL,Txt_Dates,Dat_PutIconsDateFormat, Hlp_PROFILE_Settings_dates,Box_NOT_CLOSABLE); /***** Form with list of options *****/ Frm_StartForm (ActChgDatFmt); HTM_UL_Begin ("class=\"LIST_LEFT\""); for (Format = (Dat_Format_t) 0; Format <= (Dat_Format_t) (Dat_NUM_OPTIONS_FORMAT - 1); Format++) { HTM_LI_Begin ("class=\%s\"",(Format == Gbl.Prefs.DateFormat) ? "DAT_N LIGHT_BLUE" : "DAT"); HTM_LABEL_Begin (NULL); HTM_INPUT_RADIO ("DateFormat",true, " value=\"%u\"%s", (unsigned) Format, Format == Gbl.Prefs.DateFormat ? " checked=\"checked\"" : ""); Dat_PutSpanDateFormat (Format); Dat_PutScriptDateFormat (Format); HTM_LABEL_End (); HTM_LI_End (); } /***** End list *****/ HTM_UL_End (); /***** End form *****/ Frm_EndForm (); /***** End box *****/ Box_BoxEnd (); } /*****************************************************************************/ /*************** Put contextual icons in date-format setting *****************/ /*****************************************************************************/ static void Dat_PutIconsDateFormat (void) { /***** Put icon to show a figure *****/ Gbl.Figures.FigureType = Fig_DATE_FORMAT; Fig_PutIconToShowFigure (); } /*****************************************************************************/ /******* Put script to write current date-time in a given date format ********/ /*****************************************************************************/ void Dat_PutSpanDateFormat (Dat_Format_t Format) { fprintf (Gbl.F.Out,"", (unsigned) Format); } void Dat_PutScriptDateFormat (Dat_Format_t Format) { char *Id; if (asprintf (&Id,"date_format_%u",(unsigned) Format) < 0) Lay_NotEnoughMemoryExit (); Dat_WriteLocalDateHMSFromUTC (Id,Gbl.StartExecutionTimeUTC, Format,Dat_SEPARATOR_NONE, false,true,false,0x0); free ((void *) Id); } /*****************************************************************************/ /***************************** Change date format ****************************/ /*****************************************************************************/ void Dat_ChangeDateFormat (void) { /***** Get param with date format *****/ Gbl.Prefs.DateFormat = Dat_GetParamDateFormat (); /***** Store date format in database *****/ if (Gbl.Usrs.Me.Logged) DB_QueryUPDATE ("can not update your setting about date format", "UPDATE usr_data SET DateFormat=%u" " WHERE UsrCod=%ld", (unsigned) Gbl.Prefs.DateFormat, Gbl.Usrs.Me.UsrDat.UsrCod); /***** Set settings from current IP *****/ Set_SetSettingsFromIP (); } /*****************************************************************************/ /********************** Get parameter with date format ***********************/ /*****************************************************************************/ static Dat_Format_t Dat_GetParamDateFormat (void) { return (Dat_Format_t) Par_GetParToUnsignedLong ("DateFormat", 0L, (unsigned long) (Dat_NUM_OPTIONS_FORMAT - 1), (unsigned long) Dat_FORMAT_DEFAULT); } /*****************************************************************************/ /*********************** Get date format from string *************************/ /*****************************************************************************/ Dat_Format_t Dat_GetDateFormatFromStr (const char *Str) { unsigned UnsignedNum; if (sscanf (Str,"%u",&UnsignedNum) == 1) if (UnsignedNum < Dat_NUM_OPTIONS_FORMAT) return (Dat_Format_t) UnsignedNum; return Dat_FORMAT_DEFAULT; } /*****************************************************************************/ /************************** Get current time UTC *****************************/ /*****************************************************************************/ void Dat_GetStartExecutionTimeUTC (void) { Gbl.StartExecutionTimeUTC = time (NULL); } /*****************************************************************************/ /********************** Get and convert current time *************************/ /*****************************************************************************/ void Dat_GetAndConvertCurrentDateTime (void) { struct tm *tm_ptr; /***** Convert current local time to a struct tblock *****/ tm_ptr = Dat_GetLocalTimeFromClock (&Gbl.StartExecutionTimeUTC); Gbl.Now.Date.Year = tm_ptr->tm_year + 1900; Gbl.Now.Date.Month = tm_ptr->tm_mon + 1; Gbl.Now.Date.Day = tm_ptr->tm_mday; Gbl.Now.Time.Hour = tm_ptr->tm_hour; Gbl.Now.Time.Minute = tm_ptr->tm_min; Gbl.Now.Time.Second = tm_ptr->tm_sec; /***** Initialize current date in format YYYYMMDD *****/ snprintf (Gbl.Now.Date.YYYYMMDD,sizeof (Gbl.Now.Date.YYYYMMDD), "%04u%02u%02u", Gbl.Now.Date.Year,Gbl.Now.Date.Month,Gbl.Now.Date.Day); /***** Initialize current time in format YYYYMMDDHHMMSS *****/ snprintf (Gbl.Now.YYYYMMDDHHMMSS,sizeof (Gbl.Now.YYYYMMDDHHMMSS), "%04u%02u%02u%02u%02u%02u", Gbl.Now.Date.Year,Gbl.Now.Date.Month,Gbl.Now.Date.Day, Gbl.Now.Time.Hour,Gbl.Now.Time.Minute,Gbl.Now.Time.Second); /***** Compute what day was yesterday *****/ Dat_GetDateBefore (&Gbl.Now.Date,&Gbl.Yesterday); } /*****************************************************************************/ /************ Get UNIX time (seconds since 1970 UTC) from string *************/ /*****************************************************************************/ time_t Dat_GetUNIXTimeFromStr (const char *Str) { time_t Time = (time_t) 0; if (Str) if (Str[0]) if (sscanf (Str,"%ld",&Time) != 1) Time = (time_t) 0; return Time; } /*****************************************************************************/ /*********** Get a struct Date from a string in YYYYMMDD format **************/ /*****************************************************************************/ bool Dat_GetDateFromYYYYMMDD (struct Date *Date,const char *YYYYMMDD) { if (YYYYMMDD) if (YYYYMMDD[0]) if (sscanf (YYYYMMDD,"%04u%02u%02u",&(Date->Year),&(Date->Month),&(Date->Day)) == 3) { Str_Copy (Date->YYYYMMDD,YYYYMMDD, Dat_LENGTH_YYYYMMDD); return true; } Date->Year = Date->Month = Date->Day = 0; Date->YYYYMMDD[0] = '\0'; return false; } /*****************************************************************************/ /******************** Write div for client local time ************************/ /*****************************************************************************/ void Dat_ShowClientLocalTime (void) { extern const char *Txt_Show_calendar; extern const char *Txt_Show_agenda; /***** Draw the current date and time *****/ /* Start container */ HTM_DIV_Begin ("id=\"current_date\""); /* Month with link to calendar */ HTM_DIV_Begin ("id=\"current_month\""); Frm_StartForm (ActSeeCal); Frm_LinkFormSubmit (Txt_Show_calendar,"CURRENT_MONTH",NULL); fprintf (Gbl.F.Out,"" // JavaScript will write HTML here ""); Frm_LinkFormEnd (); Frm_EndForm (); HTM_DIV_End (); /* Day with link to agenda (if I am logged) */ HTM_DIV_Begin ("id=\"current_day\""); if (Gbl.Usrs.Me.Logged) { Frm_StartForm (ActSeeMyAgd); Frm_LinkFormSubmit (Txt_Show_agenda,"CURRENT_DAY",NULL); } fprintf (Gbl.F.Out,"" // JavaScript will write HTML here ""); if (Gbl.Usrs.Me.Logged) { Frm_LinkFormEnd (); Frm_EndForm (); } HTM_DIV_End (); /* Time */ HTM_DIV_Begin ("id=\"current_time\""); // JavaScript will write HTML here HTM_DIV_End (); /* End container */ HTM_DIV_End (); /* Write script to draw the month */ HTM_SCRIPT_Begin (NULL,NULL); fprintf (Gbl.F.Out,"secondsSince1970UTC = %ld;\n" "writeLocalClock();\n", (long) Gbl.StartExecutionTimeUTC); HTM_SCRIPT_End (); } /*****************************************************************************/ /***************** Compute local time, adjusting day of week *****************/ /*****************************************************************************/ struct tm *Dat_GetLocalTimeFromClock (const time_t *timep) { struct tm *tm_ptr; if ((tm_ptr = localtime (timep)) == NULL) Lay_ShowErrorAndExit ("Can not get local time from clock."); /***** Convert from sunday, monday, tuesday... to monday, tuesday, wednesday... *****/ if (tm_ptr->tm_wday == 0) // sunday tm_ptr->tm_wday = 6; else if (tm_ptr->tm_wday >= 1 && tm_ptr->tm_wday <= 6) // monday to saturday tm_ptr->tm_wday--; else // error! tm_ptr->tm_wday = 0; return tm_ptr; } /*****************************************************************************/ /********* Convert a struct with Day, Month and Year to a date string ********/ /*****************************************************************************/ void Dat_ConvDateToDateStr (struct Date *Date,char StrDate[Cns_MAX_BYTES_DATE + 1]) { extern const char *Txt_MONTHS_SMALL_SHORT[12]; if (Date->Day == 0 || Date->Month == 0 || Date->Year == 0) StrDate[0] = '\0'; else switch (Gbl.Prefs.DateFormat) { case Dat_FORMAT_YYYY_MM_DD: snprintf (StrDate,Cns_MAX_BYTES_DATE + 1, "%04u-%02u-%02u", Date->Year, Date->Month, Date->Day); break; case Dat_FORMAT_DD_MONTH_YYYY: snprintf (StrDate,Cns_MAX_BYTES_DATE + 1, "%u %s %04u", Date->Day, Txt_MONTHS_SMALL_SHORT[Date->Month - 1], Date->Year); break; case Dat_FORMAT_MONTH_DD_YYYY: snprintf (StrDate,Cns_MAX_BYTES_DATE + 1, "%s %u, %04u", Txt_MONTHS_SMALL_SHORT[Date->Month - 1], Date->Day, Date->Year); break; } } /*****************************************************************************/ /*************** Show forms to enter initial and ending dates ****************/ /*****************************************************************************/ void Dat_PutFormStartEndClientLocalDateTimesWithYesterdayToday (bool SetHMS000000To235959) { extern const char *The_ClassFormInBox[The_NUM_THEMES]; extern const char *Txt_START_END_TIME[Dat_NUM_START_END_TIME]; extern const char *Txt_Yesterday; extern const char *Txt_Today; HTM_TR_Begin (NULL); /***** Start date-time *****/ HTM_TD_Begin ("class=\"RM\""); HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]); fprintf (Gbl.F.Out,"%s:",Txt_START_END_TIME[Dat_START_TIME]); HTM_LABEL_End (); HTM_TD_End (); /* Date-time */ HTM_TD_Begin ("class=\"LM\""); Dat_WriteFormClientLocalDateTimeFromTimeUTC ("Start", "Start", Gbl.DateRange.TimeUTC[0], Cfg_LOG_START_YEAR, Gbl.Now.Date.Year, Dat_FORM_SECONDS_ON, SetHMS000000To235959 ? Dat_HMS_TO_000000 : // Set hour, minute and second to 00:00:00 Dat_HMS_DO_NOT_SET, // Don't set hour, minute and second to 00:00:00 false); // Don't submit on change HTM_TD_End (); /***** "Yesterday" and "Today" buttons *****/ HTM_TD_Begin ("rowspan=\"2\" class=\"LM\""); HTM_INPUT_BUTTON ("Yesterday",Txt_Yesterday, "onclick=\"setDateToYesterday('Start','End');\""); HTM_INPUT_BUTTON ("Today",Txt_Today, "onclick=\"setDateToToday('Start','End');\""); HTM_TD_End (); HTM_TR_End (); HTM_TR_Begin (NULL); /***** End date-time *****/ HTM_TD_Begin ("class=\"RM\""); HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]); fprintf (Gbl.F.Out,"%s:",Txt_START_END_TIME[Dat_END_TIME]); HTM_LABEL_End (); HTM_TD_End (); /* Date-time */ HTM_TD_Begin ("class=\"LM\""); Dat_WriteFormClientLocalDateTimeFromTimeUTC ("End", "End", Gbl.DateRange.TimeUTC[1], Cfg_LOG_START_YEAR, Gbl.Now.Date.Year, Dat_FORM_SECONDS_ON, SetHMS000000To235959 ? Dat_HMS_TO_235959 : // Set hour, minute and second to 23:59:59 Dat_HMS_DO_NOT_SET, // Don't set hour, minute and second false); // Don't submit on change HTM_TD_End (); HTM_TR_End (); } /*****************************************************************************/ /************* Show forms to enter initial and ending date-times *************/ /*****************************************************************************/ void Dat_PutFormStartEndClientLocalDateTimes (time_t TimeUTC[2], Dat_FormSeconds FormSeconds) { extern const char *The_ClassFormInBox[The_NUM_THEMES]; extern const char *Txt_START_END_TIME[Dat_NUM_START_END_TIME]; Dat_StartEndTime_t StartEndTime; const char *Id[Dat_NUM_START_END_TIME] = { "Start", // Dat_START_TIME "End" // Dat_END_TIME }; for (StartEndTime = Dat_START_TIME; StartEndTime <= Dat_END_TIME; StartEndTime++) { /* Date-time */ HTM_TR_Begin (NULL); HTM_TD_Begin ("class=\"RM\""); HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]); fprintf (Gbl.F.Out,"%s:",Txt_START_END_TIME[StartEndTime]); HTM_LABEL_End (); HTM_TD_End (); HTM_TD_Begin ("class=\"LM\""); Dat_WriteFormClientLocalDateTimeFromTimeUTC (Id[StartEndTime], Id[StartEndTime], TimeUTC[StartEndTime], Gbl.Now.Date.Year - 1, Gbl.Now.Date.Year + 1, FormSeconds, Dat_HMS_DO_NOT_SET, // Don't set hour, minute and second false); // Don't submit on change HTM_TD_End (); HTM_TR_End (); } } /*****************************************************************************/ /************************* Show a form to enter a date ***********************/ /*****************************************************************************/ void Dat_WriteFormClientLocalDateTimeFromTimeUTC (const char *Id, const char *ParamName, time_t TimeUTC, unsigned FirstYear, unsigned LastYear, Dat_FormSeconds FormSeconds, Dat_SetHMS SetHMS, bool SubmitFormOnChange) { extern const char *Txt_MONTHS_SMALL[12]; unsigned Day; unsigned Month; unsigned Year; unsigned Hour; unsigned Minute; unsigned Second; unsigned MinutesIInterval[Dat_NUM_FORM_SECONDS] = { 5, // Dat_FORM_SECONDS_OFF 1, // Dat_FORM_SECONDS_ON }; char *ParamTimeUTC; /***** Begin table *****/ HTM_TABLE_Begin (NULL); HTM_TR_Begin (NULL); /***** Year *****/ HTM_TD_Begin ("class=\"RM\""); fprintf (Gbl.F.Out,""); for (Month = 1; Month <= 12; Month++) fprintf (Gbl.F.Out,"", Month,Txt_MONTHS_SMALL[Month - 1]); HTM_SELECT_End (); HTM_TD_End (); /***** Day *****/ HTM_TD_Begin ("class=\"LM\""); fprintf (Gbl.F.Out,""); for (Hour = 0; Hour <= 23; Hour++) fprintf (Gbl.F.Out,"", Hour,Hour); HTM_SELECT_End (); HTM_TD_End (); /***** Minute *****/ HTM_TD_Begin ("class=\"CM\""); fprintf (Gbl.F.Out,""); for (Second = 0; Second <= 59; Second++) fprintf (Gbl.F.Out,"", Second,Second); HTM_SELECT_End (); HTM_TD_End (); } /***** End table *****/ HTM_TR_End (); HTM_TABLE_End (); /***** Hidden field with UTC time (seconds since 1970) used to send time *****/ if (asprintf (&ParamTimeUTC,"%sTimeUTC",Id) < 0) Lay_NotEnoughMemoryExit (); Par_PutHiddenParamLong (ParamTimeUTC,ParamTimeUTC,(long) TimeUTC); free ((void *) ParamTimeUTC); /***** Script to set selectors to local date and time from UTC time *****/ HTM_SCRIPT_Begin (NULL,NULL); fprintf (Gbl.F.Out,"setLocalDateTimeFormFromUTC('%s',%ld);" "adjustDateForm('%s');", Id,(long) TimeUTC,Id); switch (SetHMS) { case Dat_HMS_TO_000000: // Set HH:MM:SS form selectors to 00:00:00 fprintf (Gbl.F.Out,"setHMSTo000000('%s');",Id); // Hidden TimeUTC is also adjusted break; case Dat_HMS_TO_235959: // Set HH:MM:SS form selectors to 23:59:59 fprintf (Gbl.F.Out,"setHMSTo235959('%s');",Id); // Hidden TimeUTC is also adjusted break; default: break; } HTM_SCRIPT_End (); } /*****************************************************************************/ /***************** Get an hour-minute time from a form ***********************/ /*****************************************************************************/ time_t Dat_GetTimeUTCFromForm (const char *ParamName) { /**** Get time ****/ return Par_GetParToLong (ParamName); } /*****************************************************************************/ /**************** Put a hidden param with time difference ********************/ /**************** between UTC time and client local time, ********************/ /**************** in minutes ********************/ /*****************************************************************************/ void Dat_PutHiddenParBrowserTZDiff (void) { Par_PutHiddenParamString ("BrowserTZName","BrowserTZName",""); Par_PutHiddenParamLong ("BrowserTZDiff","BrowserTZDiff",0); HTM_SCRIPT_Begin (NULL,NULL); fprintf (Gbl.F.Out,"setTZname('BrowserTZName');" "setTZ('BrowserTZDiff');"); HTM_SCRIPT_End (); } /*****************************************************************************/ /*************************** Get browser time zone ***************************/ /*****************************************************************************/ // MySQL CONVERT_TZ function may fail around changes related to Daylight Saving Time // when a fixed amount (for example +01:00) is used as destination time zone, // because this amount may be different before and after the DST change void Dat_GetBrowserTimeZone (char BrowserTimeZone[Dat_MAX_BYTES_TIME_ZONE + 1]) { MYSQL_RES *mysql_res; MYSQL_ROW row; bool TZNameIsUsable = false; char IntStr[1 + 10 + 1]; int ClientUTCMinusLocal; // Time difference between UTC time and client local time, in minutes /***** 1. Get client time zone name *****/ // We get client time zone using JavaScript jstz script, available in: // - http://pellepim.bitbucket.org/jstz/ // - https://bitbucket.org/pellepim/jstimezonedetect/ // The return value is an IANA zone info key (aka the Olson time zone database). // https://en.wikipedia.org/wiki/List_of_tz_database_time_zones // For example, if browser is in Madrid(Spain) timezone, "Europe/Berlin" will be returned. Par_GetParToText ("BrowserTZName",BrowserTimeZone,Dat_MAX_BYTES_TIME_ZONE); /* Check if client time zone is usable with CONVERT_TZ */ if (BrowserTimeZone[0]) { /* Try to convert a date from server time zone to browser time zone */ if (DB_QuerySELECT (&mysql_res,"can not check if time zone name" " is usable", "SELECT CONVERT_TZ(NOW(),@@session.time_zone,'%s')", BrowserTimeZone)) { row = mysql_fetch_row (mysql_res); if (row[0] != NULL) TZNameIsUsable = true; } /* Free structure that stores the query result */ DB_FreeMySQLResult (&mysql_res); } if (!TZNameIsUsable) { /***** 2. Get client time zone difference *****/ // We get client TZ difference using JavaScript getTimezoneOffset() method // getTimezoneOffset() returns UTC-time - browser-local-time, in minutes. // For example, if browser time zone is GMT+2, -120 will be returned. Par_GetParToText ("BrowserTZDiff",IntStr,1 + 10); if (sscanf (IntStr,"%d",&ClientUTCMinusLocal) != 1) ClientUTCMinusLocal = 0; /* Convert from minutes to +-hh:mm */ // BrowserTimeZone must have space for strings in +hh:mm format (6 chars + \0) if (ClientUTCMinusLocal > 0) snprintf (BrowserTimeZone,Dat_MAX_BYTES_TIME_ZONE + 1, "-%02u:%02u", (unsigned) ClientUTCMinusLocal / 60, (unsigned) ClientUTCMinusLocal % 60); else // ClientUTCMinusLocal <= 0 snprintf (BrowserTimeZone,Dat_MAX_BYTES_TIME_ZONE + 1, "+%02u:%02u", (unsigned) (-ClientUTCMinusLocal) / 60, (unsigned) (-ClientUTCMinusLocal) % 60); } } /*****************************************************************************/ /************************* Show a form to enter a date ***********************/ /*****************************************************************************/ /* See the following pages: http://javascript.internet.com/forms/date-selection-form.html http://www.java-scripts.net/javascripts/Calendar-Popup.phtml http://webmonkey.wired.com/webmonkey/98/04/index3a_page10.html?tw=programming http://www.jsmadeeasy.com/javascripts/Calendars/Select-A-Mo://tech.irt.org/articles/js068/calendar.htm http://javascript.internet.com/calendars/select-a-month.html http://javascript.internet.com/forms/country.html http://javascript.internet.com/forms/category-selection.html See also http://www.ashleyit.com/rs/jsrs/select/php/select.php */ void Dat_WriteFormDate (unsigned FirstYear,unsigned LastYear, const char *Id, struct Date *DateSelected, bool SubmitFormOnChange,bool Disabled) { extern const char *Txt_MONTHS_SMALL[12]; unsigned Year; unsigned Month; unsigned Day; unsigned NumDaysSelectedMonth; /***** Begin table *****/ HTM_TABLE_Begin (NULL); HTM_TR_Begin (NULL); /***** Year *****/ HTM_TD_Begin ("class=\"CM\""); fprintf (Gbl.F.Out,""); for (Month = 1; Month <= 12; Month++) { fprintf (Gbl.F.Out,"",Txt_MONTHS_SMALL[Month - 1]); } HTM_SELECT_End (); HTM_TD_End (); /***** Day *****/ HTM_TD_Begin ("class=\"CM\""); fprintf (Gbl.F.Out,"