// // 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 } }