When developing a framework that works across time zones and internationally, you've got to be careful with how you store and present dates and times. This chapter will show you what you need to know about all things date and time in Rock. RockDateTime vs DateTime Because a Rock server is not always running or is not configured to be in the same time zone of the organization using Rock, you should always avoid creating new date/times or the current time (aka ".Now") using the standard system DateTime. Use the RockDateTime.Now or RockDateTime.Today static class methods when creating new date/times. // Get the current date time DateTime now = RockDateTime.Now; // Get only the date component for today (wherever today is) DateTime today = RockDateTime.Today; If you're in need of checking the date/time of a file on the file-system, you should use the ConvertLocalDateTimeToRockDateTime( ... ) method to convert the time to a correctly adjusted time. DateTime fileDateTime = File.GetCreationTime( file ); DateTime adjustedDateTime = RockDateTime.ConvertLocalDateTimeToRockDateTime( fileDateTime ); These methods always work because organizations set their particular time zone when they first install Rock. Behind the scenes, Rock stored a OrgTimeZone property into their web.config which is used by the Rock DateTime helper methods. DateTime Formatting For people, there is not one correct way to format dates and times. In some countries "MM/DD/YYYY" is the standard while in others it's "DD/MM/YYYY" or "YYYY-MM-DD" or one of hundreds of other styles. However, using the incorrect format isn't just a small issue, in some cases it will change the meaning of the date value to the viewer. For the most part, the framework displays dates and times using the culture setting of the viewer's browser. However, to achieve this, when a date is stored in a string field (such as a string Attribute) you must set that string using the "Round-trip" (aka ISO 8601) format specifier "o" as shown here: // set the value to the current time thing.SetAttributeValue( attrKey, RockDateTime.Now.ToString( "o" ) ); NoteIf you're using Rock.DateTime for SQL migrations, be sure to use the ISO format for datetime strings before writing to SQL, especially in interpolated strings. For example, use '{RockDateTime.Now:s}' instead of '{RockDateTime.Now}'. Date, Time, Datetime Comparison When writing LINQ (or SQL) queries where there is a Date, Time, Date/Time, or Numeric comparison involved, it's important to be consistent in how they are written. For example, if the user wants to filter the a list of records using a Date/Time range, do you use >= and < or > and <=?. In general, we say let your start be "inclusive" and your end be "exclusive" as illustrated in each of the examples below. WarningDon't use the BETWEEN operator in SQL because it is fully inclusive and is in conflict with our "let your end be exclusive" rule. DateTime range var qry = new MyService().Queryable(); DateTime startDateTime = DateTime.Parse("11/1/2012 01:00 pm"); DateTime endDateTime = DateTime.Parse("11/2/2012 01:00 pm"); // Get the records equal to and greater than the StartDateTime, but just less than (but not equal) // the EndDateTime. qry = qry.Where( a => a.DateTime >= startDateTime && a.DateTime < endDateTime ); Date range var qry = new MyService().Queryable(); DateTime startDate = DateTime.Parse("11/1/2012"); DateTime endDate = DateTime.Parse("11/2/2012"); // For a little extra safety, since we are doing a Date comparison (not a DateTime comparison) // get just the Date portion without the time (just in case). startDate = startDate.Date; endDate = endDate.Date; // Get the records equal to and greater than the StartDate, but add a whole day to // the selected endDate since users will expect to see all the stuff that happened // on the endDate up until the very end of that day. // calculate the query endDate before including it in the qry statement to avoid Linq error endDate = endDate.AddDays(1); qry = qry.Where( a => a.DateTime >= startDate && a.DateTime < endDate ); Specific Date (of a DateTime column) var qry = new MyService().Queryable(); DateTime startDate = DateTime.Parse("11/1/2012"); // For a little extra safety, since we are doing a Date comparison (not a DateTime comparison) // get just the Date portion without the time. // (just in case) startDate = startDate.Date; // When querying for stuff that occurred on a specific date, when the data is a DateTime, // just add a day to the specific date to get records for the entire day. // calculate the query endDate before including it in the qry statement to avoid Linq error DateTime endDate = startDate.AddDays(1); qry = qry.Where( a => a.DateTime >= startDate && a.DateTime < endDate ); Specific Date (of a Date column) var qry = new MyService().Queryable(); DateTime specificDate = DateTime.Parse("11/1/2012"); // Since we are doing a Date comparison (not a DateTime comparison) get just the Date // portion without the time (just in case). specificDate = specificDate.Date; qry = qry.Where( a => a.LogDate == specificDate ); Time Range (when querying off of a Time column) var qry = new MyService().Queryable(); TimeSpan startime = TimeSpan.Parse("01:00 pm"); TimeSpan endTime = TimeSpan.Parse("02:00 pm"); // Get the records equal to and greater than the startTime, but just less // than (but not equal) the endTime. qry = qry.Where( a => a.Time >= startTime && a.Time < endTime ); Time Range (when querying off of a DateTime column) var qry = new MyService().Queryable(); TimeSpan startime = TimeSpan.Parse("01:00 pm"); TimeSpan endTime = TimeSpan.Parse("02:00 pm"); // We can't do DateTime.TimeOfDay in a queryable, so fetch it into a list first. var list = qry.ToList(); // Get the records equal to and greater than the startTime, but just less than // (but not equal) the endTime. list = list.Where( a => a.DateTime.TimeOfDay >= startTime && a.DateTime.TimeOfDay < endTime) Numeric range var qry = new MyService().Queryable(); int startValue = int.Parse("100"); int endValue = int.Parse("150"); // Unless the UI has some text that explicitly states how the values are going to be // compared, simply get the records equal to and greater than the startValue and // less than or equal to the endValue. In other words, for integer comparison, do // an "inclusive" compare qry = qry.Where( a => a.Rating >= startValue && a.Rating <= endValue; )