434 lines
18 KiB
C#
434 lines
18 KiB
C#
//
|
||
// Class: Util
|
||
// Current CLR: 4.0.30319.34209
|
||
// System: Microsoft Visual Studio 10.0
|
||
// Author: dani
|
||
// Created: 3/21/2015 10:36:56 AM
|
||
//
|
||
// Copyright (c) 2015 Informatikbüro Daniel Schick. All rights reserved.
|
||
|
||
using System;
|
||
using System.Data.SqlClient;
|
||
using System.Collections.Generic;
|
||
using System.Text.RegularExpressions;
|
||
using log4net;
|
||
using System.Globalization;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace bsmd.database
|
||
{
|
||
public static class Util
|
||
{
|
||
private static readonly Regex regexVisit = new Regex("^(DE)([A-Z]{3})-([0-9]{4})-([A-Z]{6})$", RegexOptions.IgnoreCase);
|
||
private static readonly Regex regexTransit = new Regex("^(ZZNOK)-([0-9]{4})-([A-Z]{6})$", RegexOptions.IgnoreCase);
|
||
private static readonly ILog _log = LogManager.GetLogger(typeof(Util));
|
||
|
||
/// <summary>
|
||
/// Extension helper to add values that can be null:
|
||
/// http://stackoverflow.com/questions/13451085/exception-when-addwithvalue-parameter-is-null
|
||
/// </summary>
|
||
public static SqlParameter AddWithNullableValue(this SqlParameterCollection collection, string parameterName, object value)
|
||
{
|
||
if (value == null)
|
||
return collection.AddWithValue(parameterName, DBNull.Value);
|
||
else
|
||
return collection.AddWithValue(parameterName, value);
|
||
}
|
||
|
||
public static string GetGenderDisplay(byte? val)
|
||
{
|
||
if (val.HasValue)
|
||
{
|
||
switch (val)
|
||
{
|
||
case 0: return "not known";
|
||
case 1: return "male";
|
||
case 2: return "female";
|
||
default: return "not applicable";
|
||
}
|
||
}
|
||
else
|
||
{
|
||
return "unknown";
|
||
}
|
||
}
|
||
|
||
public static string GetIdentityDocumentTypeDisplay(byte? val)
|
||
{
|
||
if(val.HasValue)
|
||
{
|
||
switch (val)
|
||
{
|
||
case 0: return "Identity card";
|
||
case 1: return "Passport";
|
||
case 2: return "Muster book";
|
||
case 3: return "Picture ID";
|
||
case 4: return "Residental permit";
|
||
case 5: return "Other legal identity document";
|
||
default: return "unknown";
|
||
}
|
||
}
|
||
else
|
||
{
|
||
return "Unknown";
|
||
}
|
||
}
|
||
|
||
public static string GetISSCTypeDisplay(byte? val)
|
||
{
|
||
if (val.HasValue)
|
||
{
|
||
switch(val)
|
||
{
|
||
case 0: return "FULL";
|
||
case 1: return "INTERIM";
|
||
default:
|
||
return "UNKNOWN";
|
||
}
|
||
}
|
||
else
|
||
{
|
||
return "Unknown";
|
||
}
|
||
}
|
||
|
||
public static string GetISSCIssuerTypeDisplay(byte? val)
|
||
{
|
||
if (val.HasValue)
|
||
{
|
||
switch (val)
|
||
{
|
||
case 0: return "ADMINISTRATION";
|
||
case 1: return "RSO";
|
||
default:
|
||
return "UNKNOWN";
|
||
}
|
||
}
|
||
else
|
||
{
|
||
return "Unknown";
|
||
}
|
||
}
|
||
|
||
public static bool IsVisitId(string val)
|
||
{
|
||
if (val.IsNullOrEmpty()) return false;
|
||
return regexVisit.IsMatch(val);
|
||
}
|
||
|
||
public static bool IsTransitId(string val)
|
||
{
|
||
if (val.IsNullOrEmpty()) return false;
|
||
return regexTransit.IsMatch(val);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Hilfsfunktion für "manuelle" Anlage eines Schiffsanlaufs. Die Objekte sind bereits gespeichert.
|
||
/// Es werden nur noch nicht vorhandene Meldeklassen erzeugt
|
||
/// </summary>
|
||
public static List<Message> CreateMessagesForCore(MessageCore core, List<Message> existingMessages, ReportingParty user = null)
|
||
{
|
||
List<Message> result = new List<Message>();
|
||
Dictionary<Message.NotificationClass, Message> messageDict = new Dictionary<Message.NotificationClass, Message>();
|
||
if(!existingMessages.IsNullOrEmpty())
|
||
{
|
||
foreach (Message aMessage in existingMessages)
|
||
{
|
||
if(messageDict.ContainsKey(aMessage.MessageNotificationClass))
|
||
{
|
||
_log.WarnFormat("Core {0} [{1}] has more than one message class for {2}", core.Id, core.DisplayId, aMessage.MessageNotificationClassDisplay);
|
||
}
|
||
messageDict[aMessage.MessageNotificationClass] = aMessage;
|
||
}
|
||
}
|
||
|
||
bool isDE, isDK;
|
||
if(core?.PoC != null)
|
||
{
|
||
isDE = core.PoC.Equals("ZZNOK") || core.PoC.StartsWith("DE");
|
||
isDK = core.PoC.StartsWith("DK");
|
||
|
||
foreach (Message.NotificationClass notificationClass in Enum.GetValues(typeof(Message.NotificationClass)))
|
||
{
|
||
if(isDE)
|
||
{
|
||
if (notificationClass == Message.NotificationClass.STO) continue;
|
||
}
|
||
if(isDK)
|
||
{
|
||
// gibt es hier etwas, das nicht gebraucht wird? (siehe Mail von Christin, 29.5.17
|
||
if ((notificationClass == Message.NotificationClass.MDH) ||
|
||
(notificationClass == Message.NotificationClass.BKRA) ||
|
||
(notificationClass == Message.NotificationClass.BKRD) ||
|
||
(notificationClass == Message.NotificationClass.TOWA) ||
|
||
(notificationClass == Message.NotificationClass.TOWD)) continue;
|
||
}
|
||
|
||
if (core.IsTransit && (notificationClass == Message.NotificationClass.VISIT)) continue;
|
||
if (!core.IsTransit && (notificationClass == Message.NotificationClass.TRANSIT)) continue;
|
||
|
||
Message message;
|
||
|
||
if (!messageDict.ContainsKey(notificationClass))
|
||
{
|
||
message = new Message();
|
||
if (user != null)
|
||
message.CreatedBy = string.Format("ENI-2: {0}", user.Logon);
|
||
message.MessageCore = core;
|
||
message.MessageCoreId = core.Id;
|
||
message.MessageNotificationClass = notificationClass;
|
||
DBManager.Instance.Save(message);
|
||
result.Add(message);
|
||
}
|
||
else
|
||
{
|
||
message = messageDict[notificationClass];
|
||
}
|
||
|
||
// abgesehen von "Listen" für die Nachrichtenklassen auch untergeordnete Elemente erzeugen, falls nicht vorhanden!
|
||
DatabaseEntity classElement;
|
||
if (!Message.IsListClass(notificationClass) && (message.Elements.Count == 0))
|
||
{
|
||
classElement = DBManager.CreateMessage(notificationClass);
|
||
|
||
// CH: 6.10.17: Für die manuelle Eingabe (wird leider nicht ganz auszuschließen sein) wäre es hilfreich, wenn alle Checkboxen nicht leer sind, sondern False beinhalten.
|
||
if(notificationClass == Message.NotificationClass.MDH)
|
||
{
|
||
((MDH)classElement).SetBoolsToFalse();
|
||
}
|
||
if(notificationClass == Message.NotificationClass.BPOL)
|
||
{
|
||
// Vorbelegung, Spezialwunsch aus BRV 5.2.18
|
||
((BPOL)classElement).CruiseShip = false;
|
||
((BPOL)classElement).StowawaysOnBoard = false;
|
||
}
|
||
if (classElement != null) // null für Visit/Transit
|
||
{
|
||
classElement.MessageHeader = message;
|
||
DBManager.Instance.Save(classElement);
|
||
message.Elements.Add(classElement);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Hilfsfunktion für "manuelle" Anlage eines Schiffsanlaufs. Die Objekte sind bereits gespeichert.
|
||
/// Es werden nur noch nicht vorhandene Meldeklassen erzeugt (Async version)
|
||
/// </summary>
|
||
public static async Task<List<Message>> CreateMessagesForCoreAsync(MessageCore core, List<Message> existingMessages, ReportingParty user = null)
|
||
{
|
||
List<Message> result = new List<Message>();
|
||
Dictionary<Message.NotificationClass, Message> messageDict = new Dictionary<Message.NotificationClass, Message>();
|
||
if (!existingMessages.IsNullOrEmpty())
|
||
{
|
||
foreach (Message aMessage in existingMessages)
|
||
{
|
||
if (messageDict.ContainsKey(aMessage.MessageNotificationClass))
|
||
{
|
||
_log.WarnFormat("Core {0} [{1}] has more than one message class for {2}", core.Id, core.DisplayId, aMessage.MessageNotificationClassDisplay);
|
||
}
|
||
messageDict[aMessage.MessageNotificationClass] = aMessage;
|
||
}
|
||
}
|
||
|
||
bool isDE, isDK;
|
||
if (core?.PoC != null)
|
||
{
|
||
isDE = core.PoC.Equals("ZZNOK") || core.PoC.StartsWith("DE");
|
||
isDK = core.PoC.StartsWith("DK");
|
||
|
||
foreach (Message.NotificationClass notificationClass in Enum.GetValues(typeof(Message.NotificationClass)))
|
||
{
|
||
if (isDE)
|
||
{
|
||
if (notificationClass == Message.NotificationClass.STO) continue;
|
||
}
|
||
if (isDK)
|
||
{
|
||
// gibt es hier etwas, das nicht gebraucht wird? (siehe Mail von Christin, 29.5.17
|
||
if ((notificationClass == Message.NotificationClass.MDH) ||
|
||
(notificationClass == Message.NotificationClass.BKRA) ||
|
||
(notificationClass == Message.NotificationClass.BKRD) ||
|
||
(notificationClass == Message.NotificationClass.TOWA) ||
|
||
(notificationClass == Message.NotificationClass.TOWD)) continue;
|
||
}
|
||
|
||
if (core.IsTransit && (notificationClass == Message.NotificationClass.VISIT)) continue;
|
||
if (!core.IsTransit && (notificationClass == Message.NotificationClass.TRANSIT)) continue;
|
||
|
||
Message message;
|
||
|
||
if (!messageDict.ContainsKey(notificationClass))
|
||
{
|
||
message = new Message();
|
||
if (user != null)
|
||
message.CreatedBy = string.Format("ENI-2: {0}", user.Logon);
|
||
message.MessageCore = core;
|
||
message.MessageCoreId = core.Id;
|
||
message.MessageNotificationClass = notificationClass;
|
||
await DBManagerAsync.Save(message);
|
||
result.Add(message);
|
||
}
|
||
else
|
||
{
|
||
message = messageDict[notificationClass];
|
||
}
|
||
|
||
// abgesehen von "Listen" für die Nachrichtenklassen auch untergeordnete Elemente erzeugen, falls nicht vorhanden!
|
||
DatabaseEntity classElement;
|
||
if (!Message.IsListClass(notificationClass) && (message.Elements.Count == 0))
|
||
{
|
||
classElement = DBManager.CreateMessage(notificationClass);
|
||
|
||
// CH: 6.10.17: Für die manuelle Eingabe (wird leider nicht ganz auszuschließen sein) wäre es hilfreich, wenn alle Checkboxen nicht leer sind, sondern False beinhalten.
|
||
if (notificationClass == Message.NotificationClass.MDH)
|
||
{
|
||
((MDH)classElement).SetBoolsToFalse();
|
||
}
|
||
if (notificationClass == Message.NotificationClass.BPOL)
|
||
{
|
||
// Vorbelegung, Spezialwunsch aus BRV 5.2.18
|
||
((BPOL)classElement).CruiseShip = false;
|
||
((BPOL)classElement).StowawaysOnBoard = false;
|
||
}
|
||
if (classElement != null) // null für Visit/Transit
|
||
{
|
||
classElement.MessageHeader = message;
|
||
await DBManagerAsync.Save(classElement);
|
||
message.Elements.Add(classElement);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
|
||
public static int? GetNumericIdentifier(ISublistElement element)
|
||
{
|
||
if (element != null)
|
||
{
|
||
string stringIdentifier = element.Identifier;
|
||
Regex re = new Regex(@"\d+");
|
||
Match m = re.Match(stringIdentifier);
|
||
if (m.Success)
|
||
{
|
||
return Int32.Parse(m.Value);
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public static bool IsIMOValid(string imoAsString)
|
||
{
|
||
if (imoAsString.IsNullOrEmpty()) return false;
|
||
string actualIMO = null;
|
||
if(imoAsString.Length == 10)
|
||
{
|
||
if (imoAsString.Substring(0, 3).Equals("imo", StringComparison.OrdinalIgnoreCase))
|
||
actualIMO = imoAsString.Substring(3);
|
||
}
|
||
if (imoAsString.Length == 7)
|
||
actualIMO = imoAsString;
|
||
|
||
if ((actualIMO != null) && Int32.TryParse(actualIMO, out int _))
|
||
{
|
||
/* The integrity of an IMO number can be verified using its check digit. This is done by multiplying
|
||
* each of the first six digits by a factor of 2 to 7 corresponding to their position from right
|
||
* to left. The rightmost digit of this sum is the check digit.
|
||
* For example, for IMO 9074729: (9×7) + (0×6) + (7×5) + (4×4) + (7×3) + (2×2) = 139
|
||
*/
|
||
|
||
int sum = 0;
|
||
for (int i = 0, multiplier = 7; i < 6; i++, multiplier--)
|
||
{
|
||
sum += (Convert.ToInt32(actualIMO.Substring(i,1)) * multiplier);
|
||
}
|
||
|
||
int lastdigit = sum % 10; // letzte Stelle
|
||
|
||
if (Convert.ToInt32(actualIMO.Substring(6,1)) == lastdigit)
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
#region CoordinateTransformation
|
||
|
||
public static double NSWToDecimalDegrees(int nswCoordinate)
|
||
{
|
||
double result = Math.Floor(nswCoordinate / 600000.0);
|
||
result += (double) (nswCoordinate % 600000.0) / 600000.0;
|
||
return result;
|
||
}
|
||
|
||
public static int DecimalDegreesToNSW(double decimalDegree)
|
||
{
|
||
int result = ((int)decimalDegree) * 600000;
|
||
result += (int) ((decimalDegree - (int)decimalDegree) * 600000);
|
||
return result;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Date calculations
|
||
|
||
// This presumes that weeks start with Monday.
|
||
// Week 1 is the 1st week of the year with a Thursday in it.
|
||
public static int GetIso8601WeekOfYear(DateTime time)
|
||
{
|
||
// Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll
|
||
// be the same week# as whatever Thursday, Friday or Saturday are,
|
||
// and we always get those right
|
||
DayOfWeek day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(time);
|
||
if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
|
||
{
|
||
time = time.AddDays(3);
|
||
}
|
||
|
||
// Return the week of our adjusted day
|
||
return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
|
||
}
|
||
|
||
public static DateTime StartOfWeek(this DateTime dt)
|
||
{
|
||
int diff = (7 + (dt.DayOfWeek - DayOfWeek.Monday)) % 7;
|
||
return dt.AddDays(-1 * diff).Date;
|
||
}
|
||
|
||
public static DateTime FirstDateOfWeekISO8601(int year, int weekOfYear)
|
||
{
|
||
DateTime jan1 = new DateTime(year, 1, 1);
|
||
int daysOffset = DayOfWeek.Thursday - jan1.DayOfWeek;
|
||
|
||
// Use first Thursday in January to get first week of the year as
|
||
// it will never be in Week 52/53
|
||
DateTime firstThursday = jan1.AddDays(daysOffset);
|
||
var cal = CultureInfo.CurrentCulture.Calendar;
|
||
int firstWeek = cal.GetWeekOfYear(firstThursday, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
|
||
|
||
var weekNum = weekOfYear;
|
||
// As we're adding days to a date in Week 1,
|
||
// we need to subtract 1 in order to get the right date for week #1
|
||
if (firstWeek == 1)
|
||
{
|
||
weekNum--;
|
||
}
|
||
|
||
// Using the first Thursday as starting week ensures that we are starting in the right year
|
||
// then we add number of weeks multiplied with days
|
||
var result = firstThursday.AddDays(weekNum * 7);
|
||
|
||
// Subtract 3 days from Thursday to get Monday, which is the first weekday in ISO8601
|
||
return result.AddDays(-3);
|
||
}
|
||
|
||
#endregion
|
||
|
||
}
|
||
}
|