public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) { AssertWriteToParameters(writer, encoder, cultureInfo); // Output the value in the General Short Date/Long Time format. var outputDate = LavaDateTime.ToString(_value, "G", cultureInfo); writer.Write(outputDate); }
/// <summary> /// Process the specified input template and verify the output against an expected DateTime result. /// </summary> /// <param name="expectedDateTime"></param> /// <param name="inputTemplate"></param> /// <param name="maximumDelta"></param> public void AssertTemplateOutputDate(ILavaEngine engine, DateTime?expectedDateTime, string inputTemplate, TimeSpan?maximumDelta = null) { AssertDateIsUtc(expectedDateTime); var outputString = GetTemplateOutput(engine, inputTemplate); var outputDateUtc = LavaDateTime.ParseToUtc(outputString, null); WriteOutputToDebug(engine, outputString); Assert.That.IsNotNull(outputDateUtc, $"Template Output does not represent a valid DateTime. [Output=\"{ outputString }\"]"); if (maximumDelta != null) { DateTimeAssert.AreEqual(expectedDateTime, outputDateUtc, maximumDelta.Value); } else { DateTimeAssert.AreEqual(expectedDateTime, outputDateUtc); } }
/// <summary> /// Returns the difference between two date/time values, measured in the specified interval. /// </summary> /// <param name="startDate">The start date.</param> /// <param name="endDate">The end date.</param> /// <param name="interval">The interval in which the difference is measured.</param> /// <returns></returns> public static Int64?DateDiff(object startDate, object endDate, string interval) { var startDto = GetDateTimeOffsetFromInputParameter(startDate, null); var endDto = GetDateTimeOffsetFromInputParameter(endDate, null); if (startDto != null && endDto != null) { var difference = endDto.Value - startDto.Value; switch (interval) { case "d": return(( Int64 )difference.TotalDays); case "h": return(( Int64 )difference.TotalHours); case "m": return(( Int64 )difference.TotalMinutes); case "M": return(( Int64 )GetMonthsBetween(startDto.Value, endDto.Value)); case "Y": // Return the difference between the dates as the number of whole years. var years = LavaDateTime.ConvertToRockDateTime(endDto.Value).TotalYears(LavaDateTime.ConvertToRockDateTime(startDto.Value)); return(years); case "s": return(( Int64 )difference.TotalSeconds); default: return(null); } } else { return(null); } }
public void EventScheduledInstanceCommand_ForSampleDataKnownEvents_ReturnsExpectedEventData() { var input = @" {% eventscheduledinstance eventid:'Customs & Classics Car Show' startdate:'2018-4-1' maxoccurrences:1 %} {% for item in EventScheduledInstances %} Name={{ item.Name }}<br> Date={{item.Date }}<br> Time={{ item.Time }}<br> DateTime={{ item.DateTime | Date:'yyyy-MM-ddTHH:mm:sszzz' }} {% endfor %} {% endeventscheduledinstance %} "; var rockTimeOffset = LavaDateTime.ConvertToRockDateTime(new DateTime(2021, 9, 1, 0, 0, 0, DateTimeKind.Unspecified)).ToString("zzz"); var expectedOutput = @" Name=Customs & Classics Car Show<br>Date=16/04/2018<br>Time=9:24 AM<br>DateTime=2018-04-16T09:24:13<offset> "; expectedOutput = expectedOutput.Replace("<offset>", rockTimeOffset); TestHelper.AssertTemplateOutput(expectedOutput, input); }
public void CalendarEventsCommand_ForSampleDataKnownEvents_ReturnsExpectedEventData() { var input = @" {% calendarevents calendarid:'Internal' startdate:'2021-1-1' maxoccurrences:2 %} {% for item in EventScheduledInstances %} Name={{ item.Name }}<br> Date={{item.Date }}<br> Time={{ item.Time }}<br> DateTime={{ item.DateTime | Date:'yyyy-MM-ddTHH:mm:sszzz' }} <hr> {% endfor %} {% endcalendarevents %} "; var rockTimeOffset = LavaDateTime.ConvertToRockDateTime(new DateTime(2021, 1, 1, 0, 0, 0, DateTimeKind.Unspecified)).ToString("zzz"); var expectedOutput = @" Name=Rock Solid Finances Class<br>Date=2/01/2021<br>Time=4:30 PM<br>DateTime=2021-01-02T16:30:00<offset><hr> Name=Rock Solid Finances Class<br>Date=3/01/2021<br>Time=12:00 PM<br>DateTime=2021-01-03T12:00:00<offset><hr> "; expectedOutput = expectedOutput.Replace("<offset>", rockTimeOffset); TestHelper.AssertTemplateOutput(expectedOutput, input); }
/// <summary> /// Converts an input value to a DateTimeOffset. /// </summary> /// <param name="input">The date.</param> /// <param name="defaultValue"></param> /// <returns></returns> private static DateTimeOffset?GetDateTimeOffsetFromInputParameter(object input, DateTimeOffset?defaultValue) { if (input is String inputString) { if (inputString.Trim().ToLower() == "now") { return(LavaDateTime.NowOffset); } else { return(LavaDateTime.ParseToOffset(inputString, defaultValue)); } } else if (input is DateTime dt) { return(LavaDateTime.ConvertToDateTimeOffset(dt)); } else if (input is DateTimeOffset inputDateTimeOffset) { return(inputDateTimeOffset); } return(defaultValue); }
/// <summary> /// Process the specified input template and verify the output against an expected DateTime result. /// </summary> /// <param name="expectedDateTime"></param> /// <param name="inputTemplate"></param> /// <param name="maximumDelta"></param> public void AssertTemplateOutputDate(ILavaEngine engine, DateTimeOffset?expectedDateTime, string inputTemplate, TimeSpan?maximumDelta = null) { var outputString = GetTemplateOutput(engine, inputTemplate); var outputDateUtc = LavaDateTime.ParseToOffset(outputString, null); WriteOutputToDebug(engine, outputString); Assert.That.IsNotNull(outputDateUtc, $"Template Output does not represent a valid DateTime. [Output=\"{ outputString }\"]"); try { if (maximumDelta != null) { DateTimeAssert.AreEqual(expectedDateTime, outputDateUtc, maximumDelta.Value); } else { DateTimeAssert.AreEqual(expectedDateTime, outputDateUtc); } } catch (Exception ex) { var info = $@" Test Environment: LavaEngine = { engine.EngineName }, LocalDateTime = {DateTimeOffset.Now}, LocalTimeZoneName = {TimeZoneInfo.Local.DisplayName}, LocalTimeZoneOffset = { TimeZoneInfo.Local.BaseUtcOffset } RockDateTime = { LavaDateTime.NowOffset }, RockTimeZoneName = { RockDateTime.OrgTimeZoneInfo.DisplayName }, RockTimeZoneOffset = { RockDateTime.OrgTimeZoneInfo.BaseUtcOffset } "; throw new Exception($"Lava Date/Time test failed.\n{ info }", ex); } }
/// <summary> /// Register value converters for Types that are not natively handled by Fluid. /// </summary> private void RegisterValueConverters() { var templateOptions = GetTemplateOptions(); /* [2021-06-24] DL * Value Converters can have a significant impact on rendering performance. * Wherever possible, a conversion function should: * 1. Process all conversions related to a specific Type domain, to avoid the need to execute similar code in multiple converters. * 2. Order from most to least frequently executed and least to most expensive execution time. * 3. Return a FluidValue as quickly as possible, to avoid executing subsequent value converters in the collection. */ // TODO: Fluid executes value converters prior to internal converters. // Performance test this implementation with short-circuiting for basic types. templateOptions.ValueConverters.Add((value) => { // If the value is an Enum, render the value name. if (value is Enum e) { return(new LavaEnumValue(e)); } // This converter cannot process the value. return(null); }); // Substitute the default Fluid DateTimeValue with an implementation that renders in the General DateTime format // rather than in UTC format. templateOptions.ValueConverters.Add((value) => { if (value is DateTime dt) { // Convert the DateTime to a DateTimeOffset value. // MinValue/MaxValue are substituted directly because they are not timezone dependent. if (dt == DateTime.MinValue) { return(new LavaDateTimeValue(DateTimeOffset.MinValue)); } else if (dt == DateTime.MaxValue) { return(new LavaDateTimeValue(DateTimeOffset.MaxValue)); } else { // Assume that the DateTime is expressed in Rock time. return(new LavaDateTimeValue(LavaDateTime.NewDateTimeOffset(dt.Ticks))); } } else if (value is DateTimeOffset dto) { return(new LavaDateTimeValue(dto)); } // This converter cannot process the value. return(null); }); // DBNull is required to process results from the Sql command. // If this type is not registered, Fluid throws some seemingly unrelated exceptions. templateOptions.ValueConverters.Add((value) => value is System.DBNull ? FluidValue.Create(null, templateOptions) : null); // Converter for a Dictionary with a non-string key type; wraps the dictionary in a proxy so it can be accessed // with a key that is not a string type. // If this converter is not registered, any attempt to access a non-standard dictionary by key returns a null value. templateOptions.ValueConverters.Add((value) => { // If the value is not a dictionary, this converter is not applicable. if (!(value is IDictionary)) { return(value); } // If the value is a standard Liquid-compatible dictionary, // return the appropriate Fluid wrapper to short-circuit further conversion attempts. if (value is IDictionary <string, object> liquidDictionary) { return(new DictionaryValue(new ObjectDictionaryFluidIndexable <object>(liquidDictionary, _templateOptions))); } var valueType = value.GetType(); // If the value is derived from LavaDataObject, no conversion is needed. if (typeof(LavaDataObject).IsAssignableFrom(valueType)) { return(value); } // If this is a generic dictionary with a string key type, no conversion is needed. if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Dictionary <,>)) { var keyType = valueType.GetGenericArguments()[0]; if (keyType == typeof(string)) { return(value); } } // Wrap the dictionary in a proxy so it can be accessed with a key that is not a string type. return(new LavaDataObject(value)); }); }
/* [2021-07-31] DL * * Lava Date filters may return DateTime, DateTimeOffset, or string values according to their purpose. * Where possible, a filter should return a DateTime value specified in UTC, or a DateTimeOffset. * Local DateTime values may give unexpected results if the Rock timezone setting is different from the server timezone. * Where a date string is accepted as an input parameter, the Rock timezone is implied unless a timezone is specified. */ /// <summary> /// Formats a date using a .NET date format string /// </summary> /// <param name="input"></param> /// <param name="format"></param> /// <returns></returns> public static string Date(object input, string format = null) { if (input == null) { return(null); } string output; // If input is "now", use the current Rock date/time as the input value. if (input.ToString().ToLower() == "now") { // To correctly include the Rock configured timezone, we need to use a DateTimeOffset. // The DateTime object can only represent local server time or UTC time. input = LavaDateTime.NowOffset; } // Use the General Short Date/Long Time format by default. if (string.IsNullOrWhiteSpace(format)) { format = "G"; } // Consider special 'Standard Date' and 'Standard Time' formats. else if (format == "sd") { format = "d"; } else if (format == "st") { format = "t"; } // If the format string is a single character, add a space to produce a valid custom format string. // (refer http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx#UsingSingleSpecifiers) else if (format.Length == 1) { format = " " + format; } if (input is DateTimeOffset inputDateTimeOffset) { // Preserve the value of the specified offset and return the formatted datetime value. output = inputDateTimeOffset.ToString(format).Trim(); } else { // Convert the input to a valid Rock DateTimeOffset if possible. DateTimeOffset?inputDateTime; if (input is DateTime dt) { inputDateTime = LavaDateTime.ConvertToRockOffset(dt); } else { inputDateTime = LavaDateTime.ParseToOffset(input.ToString(), null); } if (!inputDateTime.HasValue) { // Not a valid date, so return the input unformatted. output = input.ToString().Trim(); } else { output = LavaDateTime.ToString(inputDateTime.Value, format).Trim(); } } return(output); }
/// <summary> /// Process the specified input template and verify the output against an expected DateTime result. /// </summary> /// <param name="expectedOutput"></param> /// <param name="inputTemplate"></param> /// <param name="maximumDelta"></param> public void AssertTemplateOutputDate(string expectedRockDateTimeOutput, string inputTemplate, TimeSpan?maximumDelta = null) { AssertTemplateOutputDate(LavaDateTime.ParseToOffset(expectedRockDateTimeOutput), inputTemplate, maximumDelta); }