|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionI wanted an easier way to keep track of the television schedule for Space Shuttle missions. Prior to my seeing a need for this application, I manually entered key events, such as launch and landing, and the flight day highlights into my calendar in Outlook. NASA publishes the television schedules for Space Shuttle missions as a PDF file and as an Excel spreadsheet. In addition, NASA makes frequent revisions to the schedule during a mission. NASA broadcasts the Space Shuttle missions on satellite, and their programming is available on streaming video from their website and affiliates, direct broadcast satellite, such as Direct TV or DISH Network, and on many cable systems. During a Space Shuttle mission, NASA frequently revises their television schedule. There can be fifteen to twenty revisions for a mission, and each mission can have as many as two hundred events. That clearly demands a solution to automate. Since Microsoft published the Visual Studio Tools for Office, I saw this as a perfect opportunity to create a program that reads the television schedules in Excel format and adds the schedule to my Calendar in Outlook as appointments. The project is also located on Microsoft's open source hosting website, CodePlex, at NASA Space Shuttle TV Schedule Transfer to Outlook Calendar. Requirements
BackgroundSince this was my first project creating an application that uses Microsoft's Visual Tools for Office, I was not sure which path to pursue, NASA's schedules have a few headers of interest that the class
The Revision Header appears before any other header, and has the creation or revision date for the schedule. It appears in the first few rows explaining how to receive the satellite signal. The class captures the revision header to get the year of the mission using Date Headers have the day of the week, followed by the month, and then the day. An entry may be "MONDAY, OCTOBER 22". The date is for the day from the perspective in Houston, TX. Flight Day Headers are the flight day of the mission, and the format is "FD #", optionally followed as "/ FD (#+1)". Event Header is the header that describes the columns for the events, and includes the orbit number, subject, site, mission elapsed time, Central Time, Eastern Time, and Greenwich Mean Time. On occasions, the schedules have also included Moscow time. The columns always appear in the same column from mission to mission. However, when an Event Header is encountered, it sets the column index for each item of the information to capture. NASA LinksBelow is a screenshot of revision 0 for the STS-122 mission scheduled for launch on December 6, 2007. Rows containing the prelaunch entries are hidden.
The Download FilesFor this CodeProject article, the source files are divided into two zip files, NasaTvScheduleSrc.zip (application code) and NasaTvScheduleSrcSetup.zip (installation code). NasaTvScheduleSrc.zip contains the application code. During the development of the program, I needed to convert between time zones, something that was lacking in .NET 2.0. The MSDN Base Class Library Team published the Time Zones in the .NET Framework [Anthony Moore], providing the solution I needed to convert between time zones with their NasaTvScheduleSrcSetup.zip contains the Setup project and the Office 2003 and Office 2007 Primary Interop Assemblies. It uses code and techniques described in the MSDN article Deploying Visual Studio 2005 Tools for Office Solutions Using Windows Installer (Part 1 of 2). NasaTvScheduleApp.zip contains the output from the three projects used to create the application that was published in the original publication of the article. NasaTvScheduleSetup.zip contains the setup application used to install the application. Using the NasaStsTVSchedule Class
Create the class tvSchedule = new NasaStsTVSchedule(excelFile, viewingTimeZone);
The The
The NASA TV Schedule ApplicationThe NASA TV Schedule application uses the A second The application accesses Outlook using the using InteropOutlook = Microsoft.Office.Interop.Outlook;
There are fifteen controls that controls the application to read the schedules and manage the appointments in Outlook.
There are several key functions in the application that do the bulk of the work in reading the schedule file and maintaining the Outlook Schedule by adding and removing appointments:
Since I have saved television schedules and the revisions for the Space Shuttle missions, STS-115, STS-116, STS-117, STS-118, STS-120, STS-121, and STS-122, The code that fills the Outlook /// <summary>
/// Loads the Calendar entries from Outlook based
/// on the selected date + LookAheadWeeks (from the Settings)
/// weeks and categories selected
/// </summary>
protected void LoadOutlookSchedule()
{
dgvOutlook.Rows.Clear();
InteropOutlook.ApplicationClass outlook = null;
InteropOutlook.NameSpace nmOutlook = null;
InteropOutlook.Folder olCalendarFolder = null;
try
{
outlook = new Microsoft.Office.Interop.Outlook.ApplicationClass();
DateTime dtStart = dtpOutlook.Value;
dtStart = dtStart.Date;
const int daysInWeek = 7;
// Set an end date x weeks from the Application
// Specified Setting of LookAheadWeeks
DateTime dtEnd = dtStart.AddDays(daysInWeek *
Properties.Settings.Default.LookAheadWeeks);
string filterDateSearchRange = "([Start] >= '" +
dtStart.ToString("g", CultureInfo.CurrentCulture) +
"' AND [End] <= '" +
dtEnd.ToString("g", CultureInfo.CurrentCulture) + "')";
StringBuilder filterCategories = new StringBuilder();
string categories = GetSelectedCategories();
// Multiple categories will be checked and separated by an OR
if (categories.Length > 0)
{
string[] category = categories.Split(';');
int indexCategories;
int maxCategories = category.GetUpperBound(0);
int lowCategories = category.GetLowerBound(0);
for (indexCategories = lowCategories; indexCategories
<= maxCategories; indexCategories++)
{
filterCategories.Append("[Categories] = " +
category[indexCategories]);
// If not the only category and not the last category
if ((lowCategories != maxCategories) &&
(indexCategories < maxCategories))
{
filterCategories.Append(" OR ");
}
}
}
string filterCalendar = filterDateSearchRange;
// Put the date range search and categories search together
if (filterCategories.Length > 0)
{
filterCalendar += " AND (" + filterCategories.ToString() + ")";
}
filterCategories = null;
nmOutlook = outlook.GetNamespace("MAPI");
// Ralph Hightower - 20071104
// FolderClass, ItemClass, and AppointmentItemClass do not appear to work
// Use Folder, Item, and AppointmentItem instead
//InteropOutlook.FolderClass olCalendarFolder =
// nmOutlook.GetDefaultFolder(
// Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderCalendar)
// as InteropOutlook.FolderClass;
olCalendarFolder = nmOutlook.GetDefaultFolder(
Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderCalendar)
as InteropOutlook.Folder;
dgvOutlook.SuspendLayout();
if (olCalendarFolder != null)
{
//InteropOutlook.ItemsClass calendarItems =
// (InteropOutlook.ItemsClass)olCalendarFolder.
// Items.Restrict(filterCalendar);
InteropOutlook.Items calendarItems =
(InteropOutlook.ItemsClass)
olCalendarFolder.Items.Restrict(filterCalendar);
calendarItems.Sort("[Start]", Type.Missing);
foreach (InteropOutlook.AppointmentItem apptItem in calendarItems)
{
dgvOutlook.Rows.Add(false, apptItem.Start,
apptItem.End, apptItem.Subject, apptItem.Location);
}
}
}
catch (COMException comExp)
{
MessageBox.Show(comExp.Message + comExp.StackTrace,
Properties.Resources.ERR_COM_EXCEPTION,
MessageBoxButtons.OK, MessageBoxIcon.Exclamation,
MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
if (Properties.Settings.Default.CopyExceptionsToClipboard)
Clipboard.SetText(comExp.Message + comExp.StackTrace,
TextDataFormat.Text);
}
finally
{
dgvOutlook.ResumeLayout();
dgvOutlook.Refresh();
olCalendarFolder = null;
nmOutlook = null;
outlook = null;
}
}
Below is the code to add the schedule to Outlook: /// <summary>
/// Adds the Appointment to the Outlook Calendar
/// </summary>
/// <param name="nasaTVSchedule">Class containing
/// the information for the appointment item</param>
/// <param name="reminder">Set a reminder if true</param>
/// <param name="categories">Outlook
/// categories to file this appointment under</param>
/// <param name="outlook">The Outlook application</param>
private void AddAppointment(NasaStsTVScheduleEntry nasaTVSchedule,
bool reminder, string categories,
InteropOutlook.ApplicationClass outlook)
{
try
{
string selectedCategories = categories.Replace(";", ", ");
InteropOutlook.AppointmentItem appt =
outlook.CreateItem(
Microsoft.Office.Interop.Outlook.OlItemType.olAppointmentItem)
as InteropOutlook.AppointmentItem;
appt.Start = nasaTVSchedule.BeginDate;
appt.End = nasaTVSchedule.EndDate;
appt.Subject = nasaTVSchedule.Subject;
appt.Location = nasaTVSchedule.Site;
appt.BusyStatus = Microsoft.Office.Interop.Outlook.OlBusyStatus.olFree;
appt.Categories = selectedCategories;
appt.ReminderSet = reminder;
if (reminder)
appt.ReminderMinutesBeforeStart = 15;
appt.Importance =
Microsoft.Office.Interop.Outlook.OlImportance.olImportanceNormal;
appt.BusyStatus =
Microsoft.Office.Interop.Outlook.OlBusyStatus.olFree;
appt.Save();
nasaTVSchedule = null;
}
catch (COMException comExp)
{
MessageBox.Show(comExp.Message + comExp.StackTrace,
Properties.Resources.ERR_COM_EXCEPTION,
MessageBoxButtons.OK, MessageBoxIcon.Exclamation,
MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
if (Properties.Settings.Default.CopyExceptionsToClipboard)
Clipboard.SetText(comExp.Message +
comExp.StackTrace, TextDataFormat.Text);
}
finally
{
nasaTVSchedule = null;
}
}
The code to remove a schedule entry from Outlook: /// <summary>
/// Deletes the Appointment from the Calendar
/// </summary>
/// <param name="dtStart">Start Time of the Appointment</param>
/// <param name="dtEnd">End Time of the Appointment</param>
/// <param name="subject">Subject of the Appointment</param>
/// <param name="site">Site of the Appointment</param>
/// <param name="outlook">Outlook Application
/// to avoid opening and closing repeatedly</param>
private void RemoveAppointment(DateTime dtStart,
DateTime dtEnd, string subject, string site,
InteropOutlook.ApplicationClass outlook)
{
//
// COM Exception cause: Single quotes in Subject
// causes RemoveAppointment to get a COM Exception
// in Calendar.Items.Restrict(filterAppt)
//
string filterAppt = "([Start] = '" + dtStart.ToString("g",
CultureInfo.CurrentCulture) + "') " +
"AND ([End] = '" + dtEnd.ToString("g",
CultureInfo.CurrentCulture) + "') " +
"AND ([Subject] = '" + subject.Replace("'", "''") + "') " +
"AND ([Location] = '" + site + "')";
InteropOutlook.NameSpace nmOutlook = null;
InteropOutlook.Folder olCalendarFolder = null;
try
{
nmOutlook = outlook.GetNamespace("MAPI");
// Ralph Hightower - 20071104
// FolderClass, ItemClass, and AppointmentItemClass do not appear to work
// Use Folder, Item, and AppointmentItem instead
// InteropOutlook.FolderClass olCalendarFolder =
// nmOutlook.GetDefaultFolder(
// Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderCalendar)
// as InteropOutlook.FolderClass;
olCalendarFolder = nmOutlook.GetDefaultFolder(
Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderCalendar)
as InteropOutlook.Folder;
if (olCalendarFolder != null)
{
//InteropOutlook.ItemsClass calendarItems =
// (InteropOutlook.ItemsClass)olCalendarFolder.Items.Restrict(filterCalendar);
InteropOutlook.Items calendarItems =
(InteropOutlook.ItemsClass)olCalendarFolder.Items.Restrict(filterAppt);
calendarItems.Sort("[Start]", Type.Missing);
foreach (InteropOutlook.AppointmentItem apptItem in calendarItems)
{
apptItem.Delete();
}
}
}
catch (COMException comExp)
{
MessageBox.Show(comExp.Message + comExp.StackTrace,
Properties.Resources.ERR_COM_EXCEPTION,
MessageBoxButtons.OK, MessageBoxIcon.Exclamation,
MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
if (Properties.Settings.Default.CopyExceptionsToClipboard)
Clipboard.SetText(comExp.Message + comExp.StackTrace, TextDataFormat.Text);
}
finally
{
olCalendarFolder = null;
nmOutlook = null;
}
}
NasaStsTVSchedule ClassTo access the Excel COM objects, using InteropExcel = Microsoft.Office.Interop.Excel;
Below is the code that opens the Excel file containing the schedule and returns an /// <summary>
/// Method to open Nasa TV Schedule using Microsoft.Office.Interop.Excel
/// </summary>
public System.Array OpenExcelFile(string NasaTVScheduleFile)
{
System.Array printArea = null;
SuccessfullyOpened = false;
try
{
InteropExcelApplication = new Microsoft.Office.Interop.Excel.ApplicationClass();
InteropExcelWorkbook = (InteropExcel.WorkbookClass)
InteropExcelApplication.Workbooks.Open(NasaTVScheduleFile,
false, true, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing);
InteropExcelSheets = InteropExcelWorkbook.Worksheets;
InteropExcelWorksheet = (Microsoft.Office.Interop.Excel.Worksheet)
InteropExcelSheets.get_Item(1);
//
// COM Exception: Print_Area is not defined in spreadsheet
// (My Downloads\NASA\STS-116\tvsched_reva.xls
//
InteropExcelRange = InteropExcelWorksheet.get_Range(
Properties.Resources.NASA_PRINT_AREA, Type.Missing);
printArea = (System.Array)InteropExcelRange.Cells.Value2;
// Don't show Excel application
InteropExcelApplication.Visible = false;
SuccessfullyOpened = true;
return (printArea);
}
catch (COMException comException)
{
if (comException.TargetSite.Name ==
Properties.Resources.EXP_COMEXCEPTION_INTEROPEXCEL_OPENEXCELFILE_GETRANGE)
{
string explanation = Properties.Resources.INVALIDFILEFORMAT_NO_PRINT_AREA;
throw new InvalidFileFormatException(String.Format(explanation,
NasaTVScheduleFile), comException);
}
else
{
if (Properties.Settings.Default.CopyExceptionsToClipboard)
Clipboard.SetText(comException.Message + CRLF +
comException.StackTrace, TextDataFormat.Text);
throw;
}
}
}
The code that closes the Excel file and quits the Excel application: public void Close()
{
if (InteropExcelWorkbook != null)
InteropExcelWorkbook.Close(false, Type.Missing, Type.Missing);
if (InteropExcelApplication != null)
{
InteropExcelApplication.DisplayAlerts = false;
InteropExcelApplication.Quit();
}
}
Code that converts time in Excel to /// <summary>
/// Formats the time of the weekdayMonthDay according to Excel method (Interop or Excel)
/// </summary>
/// <param name="row">Row of spreadsheet</param>
/// <param name="weekdayMonthDay">Column of spreadsheet</param>
/// <returns>Time as a string formatted similar to DateTime.ToString("hh:mm tt")</returns>
private string ExcelFormatTime(int row, int cell)
{
string formattedTime = "";
switch (ExcelTypeInterface)
{
case ExcelInterface.InteropExcel:
formattedTime = DateTime.FromOADate((double)
TvScheduleCells.GetValue(row, cell)).ToString("hh:mm tt");
break;
case ExcelInterface.ToolsExcel:
formattedTime = ToolsExcelIF.FormatTime(TvScheduleCells, row, cell);
break;
default:
throw new ArgumentException(Properties.Resources.ERR_EXCEL_FORMAT_TIME,
Properties.Resources.ERR_ARGUMENT_TYPE_EXCEL);
}
return (formattedTime);
}
ReadScheduleRow
/// <summary>
/// Read MASA TV Schedule
/// Could generate an InvalidFileFormatException
/// </summary>
/// <returns>NasaStsTVScheduleEntry of scheduling information for event</returns>
public NasaStsTVScheduleEntry ReadScheduleRow()
{
ScheduleType entryType = ScheduleType.empty;
NasaStsTVScheduleEntry dataRow = null;
if (!SuccessfullyOpened)
{
try
{
OpenNasaTvSchedule();
}
catch (InvalidFileFormatException invalidFile)
{
NasaStsTVScheduleEntry error = new NasaStsTVScheduleEntry(
DateTime.MinValue, DateTime.MinValue, false,
invalidFile.Message, 0, invalidFile.StackTrace,
"", ScheduleType.error);
return (error);
}
}
if (SuccessfullyOpened)
{
for (; !EOF() && (entryType != ScheduleType.scheduleEntry)
&& (entryType != ScheduleType.error); CurrentRow++)
{
// Could get an InvalidFileFormatException exception
try
{
entryType = DecodeScheduleRow(CurrentRow);
}
catch (InvalidFileFormatException expInvalidFileFormat)
{
ProcessingError = expInvalidFileFormat;
entryType = ScheduleType.error;
}
}
}
if (entryType == ScheduleType.scheduleEntry)
{
CurrentRow--; // CurrentRow is incremented before testing
// the return type of DecodeScheduleCurrentRow()
dataRow = ProcessEntry(CurrentRow);
if (dataRow == null)
{
entryType = ScheduleType.empty;
}
CurrentRow++;
}
else if (entryType == ScheduleType.error)
{
dataRow = new NasaStsTVScheduleEntry(DateTime.MinValue, DateTime.MinValue, false,
ProcessingError.Message, 0, "", "", ScheduleType.error);
}
return (dataRow);
}
DecodeScheduleRow
/// <summary>
/// Decode entries in Nasa TV Schedule Excel spreadsheet
/// Could generate an InvalidFileFormatException
/// </summary>
/// <param name="row">Row for the event to decode</param>
/// <returns>Type of event</returns>
private ScheduleType DecodeScheduleRow(int row)
{
ScheduleType typeEntry = ScheduleType.empty;
object cellOrbit;
if (CurrentRow < RowCount)
{
// Year has not been initialized yet
// A revision or creation date is required
// in the spreadsheet before any headers are processed
// The revision/creation date is in the first few lines of the spreadsheet
if (Year == 0)
{
GetCreationRevisionDate();
}
cellOrbit = TvScheduleCells.GetValue(row, OrbitColumnHeader);
if (cellOrbit != null)
{
try
{
typeEntry = ProcessCellOrbit(cellOrbit, row);
}
catch (InvalidFileFormatException)
{
typeEntry = ScheduleType.error;
}
}
else
{
if (IsRowScheduleEntry(row))
typeEntry = ScheduleType.scheduleEntry;
}
}
else
{
IsEOF = true;
}
return (typeEntry);
}
ProcessCellOrbit
/// <summary>
/// Process schedule entry based on the content in column 1
/// This can have many different formats
/// 1. Comments
/// 2. Header Record (ORBIT, SUBJECT, SITE, MET, C[SD]T, E[SD]T, GMT
/// 3. Date Header (DAYOFWEEK, MONTH Day)
/// 4. Flight Day Header (FD \d*)
/// 5. Definitions (not processed)
/// </summary>
/// <param name="cellOrbit">Cell Value for Orbit column</param>
/// <param name="row">Current Row</param>
/// <returns>Type of schedule for the current row</returns>
private ScheduleType ProcessCellOrbit(object cellOrbit, int row)
{
ScheduleType typeEntry = ScheduleType.empty;
System.Type cellOrbitType = cellOrbit.GetType();
switch (cellOrbitType.FullName)
{
case "System.String":
{
string cellOrbitValue = (string)cellOrbit.ToString();
// Row contains "DEFINITION OF TERMS"
// which is the end of file; no schedule entries
// exist after this value. What remains are the
// definitions of the acronyms used in the schedule
if (cellOrbitValue.Contains(Properties.Resources.NASA_DEFINITION_OF_TERMS))
{
IsEOF = true;
typeEntry = ScheduleType.definitionOfTerms;
}
// Header: ORBIT(1) SUBJECT(3) SITE(4)
// MET(6) C[SD]T(7) E[SD]T(8) GMT(9)
// Cell number in parenthesis
else if (cellOrbitValue == Properties.Resources.NASA_ORBIT)
{
typeEntry = ScheduleType.columnHeading;
ProcessOrbitHeader(row);
}
else
{
if (MatchDateHeader(cellOrbitValue))
{
try
{
HeadingDate = ProcessDateHeader(cellOrbitValue);
typeEntry = ScheduleType.dateHeading;
}
catch (InvalidFileFormatException expInvalidFileFormat)
{
ProcessingError = expInvalidFileFormat;
}
finally
{
if (ProcessingError != null)
typeEntry = ScheduleType.error;
}
break;
// Have the Date, no need to check any other missionDay
}
// if a date heading wasn't found, look
// for Flight Day heading (FD \d .*/ FD \d)
if (MatchFlightDayHeader(cellOrbitValue))
{
typeEntry = ScheduleType.flightDayHeading;
}
}
}
break;
// Row containing orbit value must have a entry and central time,
// besides mission elapsed time and eastern time
case "System.Double":
{
// Know that Orbit column has a number
// Do the columns, Subject, Central Time, Eastern Time,
// and GMT contain String, Double, Double, Double?
if (IsRowScheduleEntry(row))
typeEntry = ScheduleType.scheduleEntry;
}
break;
default:
typeEntry = ScheduleType.empty;
break;
}
return (typeEntry);
}
ProcessEntry
/// <summary>
/// Creates a NasaStsTVScheduleEntry for schedule entries
/// </summary>
/// <param name="row">Row for the schedule to capture</param>
/// <returns>Event Schedule</returns>
private NasaStsTVScheduleEntry ProcessEntry(int row)
{
// Running into problems converting between timezones
// .Net does not have the capability
// TimeZone information is local time
DateTime dtCentral = HeadingDate;
DateTime dtBeginViewingTime;
DateTime dtEndViewingTime = HeadingDate;
Changed = false;
// Column 2 will contain an asterisk if an item has changed
bool validEntry = false;
NasaStsTVScheduleEntry entryRow = null;
// If there has been a flight missionDay heading process
if (!((HeadingDate.Year == 1) && (HeadingDate.Month == 1)
&& (HeadingDate.Day == 1)
&& (HeadingDate.Hour == 0) && (HeadingDate.Minute == 0)))
{
object cellTwo = TvScheduleCells.GetValue(row, 2);
if (cellTwo != null)
{
System.Type cellTwoType = cellTwo.GetType();
if (cellTwoType.FullName == Properties.Resources.SYSTEM_STRING)
{
string cellTwoValue = cellTwo.ToString();
Changed = (cellTwoValue == Properties.Resources.NASA_CHANGED);
}
}
Subject = GetMultiLineSubject(row);
// State variables for docking and in space
// are not reliable for schedule revisions
// published after launch or docking
if (Subject.Contains(Properties.Resources.NASA_DOCKING))
{
if (!Subject.Contains(Properties.Resources.NASA_VTR_PLAYBACK))
{
Docked = true;
}
}
else if (Subject.Contains(Properties.Resources.NASA_UNDOCKING) ||
Subject.Contains(Properties.Resources.NASA_UNDOCKS))
{
if (!Subject.Contains(Properties.Resources.NASA_VTR_PLAYBACK))
{
Docked = false;
}
}
else if (Subject == Properties.Resources.NASA_LAUNCH)
{
if (!Subject.Contains(Properties.Resources.NASA_VTR_PLAYBACK))
{
InOrbit = true;
}
}
else if (Subject.Contains(Properties.Resources.NASA_LANDING))
{
if (!Subject.Contains(Properties.Resources.NASA_VTR_PLAYBACK))
{
InOrbit = false;
Landed = true;
}
}
if (TvScheduleCells.GetValue(row, SiteColumHeader) != null)
Site = TvScheduleCells.GetValue(row, SiteColumHeader).ToString();
else
{
if (Docked)
Site = Properties.Resources.NASA_ISS;
else
Site = Properties.Resources.NASA_STS;
if (Subject.Contains(Properties.Resources.NASA_CREW_SLEEP_BEGINS) ||
Subject.Contains(Properties.Resources.NASA_CREW_WAKE_UP))
{
if (ISSCrewSleep(row) || ISSCrewWakeUp(row))
Site = Properties.Resources.NASA_ISS;
if (ShuttleCrewSleep(row) || ShuttleCrewWakeUp(row))
Site = Properties.Resources.NASA_STS;
}
}
if (TvScheduleCells.GetValue(row, MissionElapsedTimeColumnHeader) != null)
{
MissionElapsedTime = TvScheduleCells.GetValue(row,
MissionElapsedTimeColumnHeader).ToString();
MissionDurationTime.Set(TvScheduleCells, row, MissionElapsedTimeColumnHeader);
}
// watch for "NET L" usually means "Net Landing + some time"
if (TvScheduleCells.GetValue(row, OrbitColumnHeader) != null)
Orbit = (System.Double)TvScheduleCells.GetValue(row, OrbitColumnHeader);
if (TvScheduleCells.GetValue(row, FlightDayColumnHeader) != null)
FlightDay = TvScheduleCells.GetValue(row, FlightDayColumnHeader).ToString();
if (TvScheduleCells.GetValue(row, CentralTimeColumnHeader) != null)
{
CentralTime = ExcelFormatTime(row, CentralTimeColumnHeader);
dtBeginViewingTime = ConvertFromCentralTzToViewingTz(dtCentral, CentralTime);
dtEndViewingTime = GuesstimateFixedEvents(Subject, dtBeginViewingTime);
// If a special event was not found, get the start time for the next event
if (dtEndViewingTime == DateTime.MinValue)
dtEndViewingTime = ReadAhead();
validEntry = true;
// This situation may not occur (except for STS
// Landing since though there are next events,
// the events are Net Landing + a time span
// If the end time occurs before the beginning time, assume 30 minutes
if (dtBeginViewingTime > dtEndViewingTime)
dtEndViewingTime = dtBeginViewingTime.AddHours(1);
entryRow = new NasaStsTVScheduleEntry(dtBeginViewingTime,
dtEndViewingTime, Changed, Subject,
Orbit, Site, FlightDay, ScheduleType.scheduleEntry);
}
}
if (validEntry)
{
return (entryRow);
}
else
return (null);
}
Exceptions Generated
NasaStsTvScheduleEntryThe
ScheduleType/// <summary>
/// Enum to interpret the different types of rows
/// in the Space Shuttle TV Schedule spreadsheet
/// empty: blank
/// columnHeading: for the row containing the column header for the schedule events
/// dateHeading: changes the Date
/// flightDayHeading: header for the Flight Day
/// scheduleEntry: the event with start and end times
/// The Subject may be on multiple lines in
/// the same column, other column entries will be
/// blank if the subject is continued
/// definitionOfTerms: end of file, definitions are skipped
/// </summary>
public enum ScheduleType
{
empty, columnHeading, dateHeading, flightDayHeading, scheduleEntry,
definitionOfTerms, error
};
Points of InterestTime Zone ConversionsThe schedule is published from a Houston, Texas point of view since that is the location of the Johnson Space Center. I did not want to do the date arithmetic to advance the day when midnight arrived in Eastern Time and Houston, TX was still 11 PM. I decided to have the program use time zone conversions. That was when I discovered that .NET 2.0 did not handle conversions between time zones! One can do conversions between the local time zone and Universal Coordinated Time all day long; but there is no method to convert between time zones in .NET 2.0. I searched on MSDN, and found the Switching from Daylight Saving Time to Standard TimeDuring the mission of STS-120 from October 23, 2007 through November 7, 2007, there were events scheduled during that hour period where “time does not exist”, the hour of transition from Daylight Saving Time to Standard Time on November 4, 2007. The time in Houston, Texas was still in Daylight Saving Time, and South Carolina was in Standard Time. I had to put a special case in the routine that converted Central Time to the viewer’s time zone for Eastern Time. /// <summary>
/// The Nasa TV Schedule is Houston-centric.
/// This is an easy method to convert from Central to
/// other time zones
/// There is a kludge for that 2 AM hour that
/// does not occur when Daylight Savings Time ends
/// and Standard Time begins for Eastern Time
///
/// Uses TimeZoneInfo developed by Microsoft MSDN BCL Team
/// </summary>
/// <param name="dtConvert">Date of the event in Central Time Zone</param>
/// <param name="timeOfday">Time of the event in Central Time Zone</param>
/// <returns>DateTime in Viewer's Time Zone</returns>
private DateTime ConvertFromCentralTzToViewingTz(DateTime dtConvert,
string timeOfday)
{
string convertTime = timeOfday.Trim();
DateTime dtCentralTZ = dtConvert.Date;
DateTime dtTimeOfDay = DateTime.Parse(convertTime, CultureInfo.CurrentCulture);
dtCentralTZ = dtCentralTZ.Add(dtTimeOfDay.TimeOfDay);
DateTime dtViewingTZ = TimeZoneInfo.ConvertTimeZoneToTimeZone(dtCentralTZ,
JohnsonSpaceCenterTZ, ViewingTimeZoneTZ);
// Kludge for Eastern Daylight Time transition to Eastern Standard Time
if ((dtCentralTZ.Hour == dtViewingTZ.Hour) &&
(ViewingTimeZoneTZ.DisplayName == Properties.Resources.TZ_US_EASTERN))
dtViewingTZ = dtViewingTZ.AddHours(1);
return (dtViewingTZ);
}
Regular ExpressionsI had experience with regular expressions from when I programmed on Unix systems. Regular expressions got a workout in this program. Regular expressions were used for the following:
An innovative use of regular expressions was used in the function Regular expressions #1 and #2 look the same, however, in #1, the
/// <summary>
/// Helper method used by:
/// 1. ShuttleCrewSleepBegins
/// 2. ShuttleCrewWakeup
/// 3. ISSCrewSleepBegins
/// 4. ISSCrewWakeUp
/// 5. EVABegins
/// 6. EVAEnds
/// </summary>
/// <param name="rgSubjectVerbPattern">Regular expression
/// for required rgSubjectVerbPattern:
// Shuttle or ISS</param>
/// <param name="subject">Crew: Shuttle or ISS</param>
/// <param name="verb">CREW WAKE UP or CREW SLEEP BEGINS</param>
/// <param name="row">Row in TvScheduleCells with Subject to match</param>
/// <returns>true if Required Crew is in the desired Sleep or Wake Activity</returns>
private bool SubjectVerbPatternMatch(Regex rgSubjectVerbPattern,
string subject, string verb, int row)
{
string entry = TvScheduleCells.GetValue(row, SubjectColumnHeader).ToString();
Match mtchSubjectVerb = rgSubjectVerbPattern.Match(entry);
GroupCollection grpcollSubjectVerb = mtchSubjectVerb.Groups;
bool matchSubjectVerb = grpcollSubjectVerb[subject].Success &&
(grpcollSubjectVerb[Properties.Resources.IX_ACTIVITY].Success &&
(grpcollSubjectVerb[Properties.Resources.IX_ACTIVITY].ToString() == verb));
return (matchSubjectVerb);
}
History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||