NASA 航天飞机电视节目表传输到 Outlook 日历






4.77/5 (9投票s)
NASA航天飞机任务时间表读取NASA发布的包含航天飞机任务电视时间表的Excel文件,并提供将时间表输入和更新到Outlook日历的功能。
引言
我想找到一种更简单的方法来跟踪航天飞机任务的电视时间表。在我看到此应用程序的需求之前,我手动将发射和着陆等关键事件以及飞行日亮点输入到我的Outlook日历中。
NASA发布航天飞机任务的电视时间表,格式为PDF文件和Excel电子表格。此外,NASA在任务期间会频繁修改时间表。NASA通过卫星广播航天飞机任务,其节目可以在其网站和关联公司的流媒体视频、直接广播卫星(如Direct TV或DISH Network)以及许多有线电视系统上观看。
在航天飞机任务期间,NASA会频繁修改其电视时间表。一个任务可能会有十五到二十次修订,而每个任务可能多达两百个事件。这显然需要一个自动化解决方案。
自Microsoft发布Visual Studio Tools for Office以来,我看到这是一个绝佳的机会来创建一个程序,该程序读取Excel格式的电视时间表,并将时间表作为约会添加到我的Outlook日历中。
该项目也位于Microsoft的开源托管网站CodePlex上,网址为NASA Space Shuttle TV Schedule Transfer to Outlook Calendar。
要求
- Microsoft Office:Excel和Outlook
- Microsoft Office主要互操作程序集
背景
由于这是我创建的第一个使用Microsoft Visual Tools for Office的应用程序项目,我不确定应该选择哪种方式,是Microsoft.Office.Tools.Excel
还是Microsoft.Office.Interop.Excel
。在程序开发过程中,我一直无法使用Microsoft.Office.Tools.Excel
打开文件;但是使用Microsoft.Office.Interop.Excel
却成功了。
NASA的时间表中有几个值得关注的标题,NasaStsTvSchedule
类会解析它们。
- 修订标题
- 日期标题
- 飞行日标题
- 事件标题
修订标题出现在任何其他标题之前,包含时间表的创建或修订日期。它出现在前几行,解释如何接收卫星信号。该类捕获修订标题以使用GetCreationRevisionDate
获取任务的年份。
日期标题包含星期几、月份和日期。一个条目可能是“MONDAY, OCTOBER 22”。日期是从休斯顿(德克萨斯州)的角度来看的。ProcessDateHeader
设置DateTime
的月份和日期。
飞行日标题是任务的飞行日,格式为“FD #”,可选地后跟“/ FD (#+1)”。
事件标题是描述事件列的标题,包括轨道号、主题、地点、任务已用时间、中部时间、东部时间和格林威治标准时间。有时,时间表还包含莫斯科时间。从任务到任务,这些列的出现位置总是相同的。但是,当遇到事件标题时,它会为要捕获的每个信息项设置列索引。
NASA链接
下面是STS-122任务(计划于2007年12月6日发射)的修订0的截图。包含发射前条目的行已隐藏。
下载文件
对于这个CodeProject文章,源文件分为两个zip文件:NasaTvScheduleSrc.zip(应用程序代码)和NasaTvScheduleSrcSetup.zip(安装代码)。
NasaTvScheduleSrc.zip包含应用程序代码。在程序开发过程中,我需要进行时区转换,而.NET 2.0在这方面有所欠缺。MSDN Base Class Library Team发布了.NET Framework中的时区[Anthony Moore],提供了我需要的解决方案,即使用他们的TimeZoneInfo
类进行时区转换,该类包含在.NET 3.5中。
NasaTvScheduleSrcSetup.zip包含Setup项目以及Office 2003和Office 2007主要互操作程序集。它使用了MSDN文章使用Windows Installer部署Visual Studio 2005 Tools for Office解决方案(第一部分,共两部分)中描述的代码和技术。
NasaTvScheduleApp.zip包含用于创建应用程序的三个项目生成的输出,该应用程序在文章的原始出版物中发布。
NasaTvScheduleSetup.zip包含用于安装应用程序的安装程序。
使用NasaStsTVSchedule类
NasaStsTVSchedule
使用Microsoft.Office.Interop.Excel
打开Excel电子表格并获取包含时间表的单元格。
创建NasaStsTVSchedule
类,该类通过以下方式读取和处理Excel电子表格中的电视时间表文件:
tvSchedule = new NasaStsTVSchedule(excelFile, viewingTimeZone);
viewingTimeZone
是在SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones中的显示值。东部标准时间(Eastern Standard Time)的时区是“(GMT-05:00) Eastern Time (US & Canada)”。
ReadScheduleRow
返回一个NasaStsTVScheduleEntry
,其中包含添加到Outlook日历所需的以下信息:
BeginDate
- 开始日期和时间EndDate
- 结束日期和时间Subject
- 电视播放的事件Orbit
- 完成的轨道数Site
- 事件发生的地点FlightDay
- 任务的飞行日Changed
- 指示此条目是否与之前发布的日程表有所更改TypeEntry
- 返回记录的ScheduleType
;scheduleEntry
包含调度所需的信息;而error
应被检查以确定在读取Excel文件时是否发生错误
NASA电视时间表应用程序
NASA电视时间表应用程序使用NasaStsTVSchedule
类读取Excel电子表格并填充DataGridView
。每行都包含一个复选框,用于指示包含日程信息的那一行将被添加到Outlook的日历中作为约会。另一个复选框用于设置约会条目的提醒。
第二个DataGridView
包含Outlook中的约会项,这些约会项与指定的搜索条件匹配。搜索条件是选定的类别以及选定的日期加上未来四周。复选框列指示将从Outlook中删除的约会项。
该应用程序使用Microsoft.Office.Interop.Outlook
COM对象访问Outlook。
using InteropOutlook = Microsoft.Office.Interop.Outlook;
有十五个控件控制着应用程序读取时间表和管理Outlook中的约会。
- Excel控件
- 打开NASA电视时间表 - 打开Excel文件并填充Excel
DataGridView
。 - 全选 - 选择Excel
DataGridView
中的所有行。 - 取消全选 - 取消选择Excel
DataGridView
中的所有行。 - Excel
DataGridView
- 包含电视时间表中的事件时间表;包括用于选择行和在将事件转移到Outlook时添加到Outlook提醒的复选框。
- 打开NASA电视时间表 - 打开Excel文件并填充Excel
- Outlook控件
- 任务开始日期 -
DateTimePicker
设置Outlook搜索约会的开始日期;结束日期是从开始日期起四周。 - 观看时区 - 包含可用时区的
ComboBox
。选定的时区用于将中部时间转换为ExcelDataGridView
的时间。 - Outlook类别 - 包含约会可以关联的类别的
CheckListBox
。我创建了一个名为“NASA STS TV Schedule”的类别,用于创建约会。 - 全选 - 选择Outlook
DataGridView
中的所有行。 - 取消全选 - 取消选择Outlook
DataGridView
中的所有行。 - 智能选择 - 使用Excel
DataGridView
中的第一个和最后一个条目的开始日期和时间,并在OutlookDataGridView
中选择落在该时间段内的条目。 - 转移电视时间表 - 从Excel
DataGridView
时间表中选定的条目创建Outlook中的约会。 - 删除选定条目 - 从Outlook中删除选定的约会。
- 批量导入 - 打开多个时间表并将时间表转移到Outlook。
- 新时间表更新 - 提供了一种简单的方法来读取修订的时间表文件并更新Outlook日历。
- 刷新类别 - 重新加载Outlook类别。
- Outlook
DataGridView
- 包含匹配Outlook类别和日期搜索范围搜索条件的Outlook日历条目。
- 任务开始日期 -
应用程序中有几个关键函数负责大部分工作,包括读取时间表文件和通过添加和删除约会来维护Outlook时间表。
OpenNasaTvSchedule()
使用MicrosoftOpenFileDialog
选择要加载的Excel文件,并调用LoadExcelSchedule(excelSchedule)
来读取时间表文件。LoadExcelSchedule(excelSchedule)
读取并解析Excel时间表文件,以填充包含NASA任务时间表的DataGridView
。LoadOutlookSchedule()
加载包含指定日期范围和Outlook类别的Outlook日历条目的DataGridView
。RemoveOutlookEntries()
从Outlook的日历中删除指定的条目。TransferExcelToOutlook()
将NASA时间表DataGridView
中的指定条目添加到Outlook的日历中。SmartSelect()
使用NASA时间表DataGridView
的第一行和最后一行来获取日期/时间范围,并使用该日期和时间来选择OutlookDataGridView
中落在该日期/时间范围内的条目。SelectAllExcel()
选择NASA时间表DataGridView
中的所有条目。UnselectAllExcel
做相反的操作。SelectAllOutlook()
选择OutlookDataGridView
中的所有条目。UnselectAllOutlook
做相反的操作。
由于我保存了航天飞机任务(STS-115、STS-116、STS-117、STS-118、STS-120、STS-121和STS-122)的电视时间表及其修订版,因此BulkImport
提供了一种简便的方法将任务时间表从Excel转移到Outlook。
填充Outlook DataGridView
的代码
/// <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;
}
}
以下是将时间表添加到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;
}
}
从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类
为了访问Excel COM对象,NasaStsTVSchedule
使用Microsoft.Office.Interop.Excel
。
using InteropExcel = Microsoft.Office.Interop.Excel;
NasaStsTvSchedule 的公共方法 |
描述 |
---|---|
NasaStsTVSchedule(string excelFile, string viewingTimeZone) |
使用NASA电视时间表的文件名和观看时区初始化类。 |
NasaStsTVScheduleEntry ReadScheduleRow() |
返回包含事件调度详情的NasaStsTVScheduleEntry 。 |
bool EOF() |
如果已到达电子表格末尾,则返回true 。 |
void Close() |
关闭电子表格和Excel。 |
bool InSpace() |
如果航天飞机在轨道上,则返回true 。 |
bool IsDocked() |
如果航天飞机已对接国际空间站,则返回true (与修订版时间表对接后可能不准确)。 |
下面是打开包含时间表的Excel文件并返回包含时间表单元格的行数组的代码。通常,NASA创建的所有电子表格文件都有一个名为“Print_Area”的名称,该名称定义了包含时间表的单元格范围。但是,有时NASA会忘记定义该名称;在这种情况下,会抛出InvalidFileFormatException
。该异常会被捕获,并将错误消息通过ReadScheduleRow
返回的NasaStsTvScheduleEntry
传递回应用程序。
/// <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;
}
}
}
关闭Excel文件并退出Excel应用程序的代码
public void Close()
{
if (InteropExcelWorkbook != null)
InteropExcelWorkbook.Close(false, Type.Missing, Type.Missing);
if (InteropExcelApplication != null)
{
InteropExcelApplication.DisplayAlerts = false;
InteropExcelApplication.Quit();
}
}
将Excel时间转换为DateTime
的代码
/// <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
ReadScheduleRow
在尚未打开电视时间表文件的情况下打开它,并逐行遍历电子表格。它调用DecodeScheduleRow
来确定和解码各种类型的标题。如果当前行是scheduleEntry
,则调用ProcessEntry
来创建要返回的NasaStsTVScheduleEntry
记录。
/// <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
DecodeScheduleRow
返回当前行的记录类型。任务的年份在电子表格的前几行设置,在遇到任何标题记录之前。如果当前行的第一个单元格是一个匹配“MM/DD/YY”日期格式的字符串,则通过GetCreationRevisionDate
捕获年份。ProcessCellOrbit
返回记录的类型。
/// <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
当当前行的第一列包含值时,调用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
ProcessEntry
从当前行和设置日期标题记录的月份和日期的前一行收集信息。它调用ReadAhead()
来获取当前事件的结束时间和日期。有些事件的持续时间比下一个计划事件长,例如宇航员睡眠时间和EVA;对于这些事件,会跳过下一个发生的事件,直到遇到匹配的结束事件。同样,有些事件的持续时间不会持续到下一个计划事件。这些事件是飞行日亮点(通常持续30到45分钟)、新闻发布会、采访和任务简报。对于这些事件,GuesstimateFixedEvents
使用EventTimes
数组来获取事件的持续时间。
/// <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);
}
生成的异常
NasaStsTVSchedule
基于以下两种情况之一抛出InvalidFileFormatException
:
- 未找到日程修订或创建日期。需要修订或创建日期来获取任务的年份。如果在找到任何常规标题记录之前未找到日期,则会抛出
InvalidFileFormatException
。原因是:“在文件 {0} 中未找到创建或修订日期。无法确定任务的年份。” - NASA时间表通常有一个名为“Print_Area”的单元格范围,该范围定义了时间表的区域。如果电子表格中未定义名为Print_Area的单元格范围,则会抛出
InvalidFileFormatException
。原因是:“在NASA STS电视时间表文件 {0} 中未定义范围Print_Area”。
NasaStsTvScheduleEntry
NasaStsTvScheduleEntry
包含在Outlook中创建调度所需的所有信息。
NasaStsTvScheduleEntry 的公共方法、属性 |
描述 |
---|---|
NasaStsTvScheduleEntry(DateTime entryBeginDateTime, DateTime entryEndDateTime, bool entryRevised, string entrySubject, double entryOrbit, string entrySite, string entryFlightDay, ScheduleType entryType) |
事件日程表的构造函数,包含开始和结束日期时间、修订指示符、主题、事件的轨道号、事件地点、飞行日和条目类型。 |
DateTime BeginDate |
事件开始的日期和时间。 |
DateTime EndDate |
事件结束的日期和时间。 |
string Subject |
事件主题。 |
double Orbit |
返回事件的轨道号。 |
string Site |
事件的地点。 |
string FlightDay |
事件发生的飞行日。 |
bool Changed |
指示事件是否与之前发布的日程表有所更改。 |
ScheduleType TypeEntry |
返回条目类型:columnHeading 、dateHeading 、flightDayHeading 、scheduleEntry 、empty 或error 。 |
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
};
关注点
时区转换
时间表是从休斯顿(德克萨斯州)的角度发布的,因为那是约翰逊航天中心的所在地。我不想进行日期计算来推进日期,因为当东部时间午夜到达时,休斯顿(德克萨斯州)仍然是晚上11点。我决定让程序使用时区转换。那时我发现.NET 2.0无法处理时区之间的转换!你可以轻松地在本地时区和协调世界时之间进行转换;但是.NET 2.0中没有在时区之间进行转换的方法。我在MSDN上搜索,并找到了Base Class Library团队在其博客上发布的TimeZoneInfo
。 .NET 3.5框架中有TimeZoneInfo
,但在我开发此程序时,它处于beta发布状态。
从夏令时切换到标准时
在STS-120任务期间,从2007年10月23日到2007年11月7日,在“不存在时间”的这一个小时内安排了事件,即从夏令时转换为标准时间的过渡时段,在2007年11月4日。休斯顿(德克萨斯州)的时间仍然是夏令时,而南卡罗来纳州是标准时。我必须在将中部时间转换为查看者时区(东部时间)的例程中添加一个特殊情况。
/// <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);
}
正则表达式
我曾有过使用正则表达式的经验,那是在我编写Unix系统程序时。在这个程序中,正则表达式得到了充分利用。
正则表达式用于以下方面:
- 日期标题,用于捕获事件的月份和日期。
- 修订日期,用于捕获任务的年份。
- EVA活动 - 开始和结束。
- 飞行日标题
- 飞行日亮点 - 当天事件的回顾。
- ISS宇航员睡眠活动 - 睡眠开始和起床。
- 航天飞机宇航员睡眠活动 - 睡眠开始和起床。
正则表达式的一个创新用途是在SubjectVerbPatternMatch
函数中,它用于配对开始和结束活动,例如“SHUTTLE CREW SLEEP BEGINS”与“SHUTTLE CREW WAKE UP”。同样,该函数也用于匹配ISS宇航员的睡眠时段和ISS宇航员的起床时间,并将“EVA BEGINS”与“EVA ENDS”配对。对于宇航员睡眠时段开始或EVA开始的ReadAhead
逻辑将搜索宇航员起床时间或EVA结束时间来获取结束时间。主题是< Shuttle > < ISS >或< Eva >,活动是“crew sleep begins”和“crew wake up”,或者EVA活动是“begins”或“ends”。
正则表达式#1和#2看起来相同,但是,在#1中,Shuttle
是必需的,而ISS
是可选的;这用于航天飞机宇航员的睡眠时段和起床时间。在正则表达式#2中,Shuttle
是可选的,而ISS
是必需的,用于ISS宇航员的睡眠时段和宇航员起床时间。以下是使用的正则表达式:
- (?<
Shuttle
>ATLANTIS|DISCOVERY|ENDEAVOUR)(?:\s*/?\s*)?(?<ISS
>ISS)?(?:\s*)(?<Activity
>CREW SLEEP BEGINS|CREW WAKE UP) - (?<
Shuttle
>ATLANTIS|DISCOVERY|ENDEAVOUR)?(?:\s*/?\s*)?(?<ISS
>ISS)(?:\s*)(?<Activity
>CREW SLEEP BEGINS|CREW WAKE UP) - (?<
Eva
>EVA)\s+(?<Number>#\d+)\s+(?<Activit
y>BEGINS|ENDS)
/// <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);
}
历史
- 2007年12月3日。初始版本。
- 2007年12月21日。
- 修复了在航天飞机宇航员和ISS宇航员的起床时间不同时,找不到航天飞机宇航员起床电话的bug。
- 根据CodeProject读者的建议,添加了“新时间表更新”。这是一个绝佳的建议,使得程序更容易使用。
- 添加了安装设置项目和用于下载的设置应用程序。
- 2007年12月23日
- 修复了正则表达式HTML标记的格式。
- 2007年12月29日
- 添加了关于
InvalidFileFormatException
的部分。 - 修复了正则表达式中Endeavour的拼写错误,在
ReadAhead(...)
之后恢复了任务月份和年份;收紧了新年滚动的规则。 - 更新了源代码、安装代码和编译代码。
- 添加了关于
- 2008年12月01日
- 添加了最新下载链接