//
// 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));
///
/// Extension helper to add values that can be null:
/// http://stackoverflow.com/questions/13451085/exception-when-addwithvalue-parameter-is-null
///
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);
}
///
/// Hilfsfunktion für "manuelle" Anlage eines Schiffsanlaufs. Die Objekte sind bereits gespeichert.
/// Es werden nur noch nicht vorhandene Meldeklassen erzeugt
///
public static List CreateMessagesForCore(MessageCore core, List existingMessages, ReportingParty user = null)
{
List result = new List();
Dictionary messageDict = new Dictionary();
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;
}
///
/// Hilfsfunktion für "manuelle" Anlage eines Schiffsanlaufs. Die Objekte sind bereits gespeichert.
/// Es werden nur noch nicht vorhandene Meldeklassen erzeugt (Async version)
///
public static async Task> CreateMessagesForCoreAsync(MessageCore core, List existingMessages, ReportingParty user = null)
{
List result = new List();
Dictionary messageDict = new Dictionary();
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
}
}