Table of Contents


DateTimeUtil Class Reference

Overview

The DateTimeUtil class is a static utility class that provides comprehensive date and time handling functionality for Magentrix applications. This class enables developers to work with dates and times while properly managing timezone conversions, user preferences, and fiscal period calculations.

Key Features:

  • Timezone conversion between UTC and user local time
  • Relative time formatting (e.g., "5 minutes ago")
  • Fiscal year and quarter calculations
  • User timezone preference management
  • Date/time manipulation utilities

Core Concepts

UTC Storage Model

Magentrix stores all date and time values in UTC (Coordinated Universal Time) format in the database. This ensures consistency across different timezones and eliminates ambiguity when users are distributed globally.

Why UTC Storage?

  • Provides a single, unambiguous timestamp for all records
  • Eliminates daylight saving time complications in storage
  • Enables accurate timezone conversions for any user location
  • Simplifies date/time calculations and comparisons

User Timezone Preferences

Each user can configure their preferred timezone in their user preferences. If no user timezone is set, the system falls back to the company's default timezone configured in Setup > Company Preferences.

Timezone Preference Hierarchy:

  1. User Timezone - Individual user preference (highest priority)
  2. Company Timezone - Organization default (fallback)

Automatic vs Manual Conversions

Automatic Conversions: Magentrix UI components such as FieldDateTime and InputField automatically handle timezone conversions. These components:

  • Display UTC database values in the user's local timezone
  • Convert user input back to UTC before saving to the database
  • No developer intervention required for standard UI scenarios

Manual Conversions: The DateTimeUtil class is required when:

  • Working with dates/times in custom controllers or business logic
  • Processing dates programmatically outside of UI components
  • Building custom APIs or data exports
  • Implementing custom date formatting or calculations

Methods Summary

Method & SignatureReturn TypeDescription
ConvertFromUTC(DateTime utcTime)DateTimeConverts UTC time to current user's local time
ConvertToUTC(DateTime value)DateTimeConverts user's local time to UTC for database storage
ConvertToUserTime(DateTime utcDateTime,
string timezone)
DateTimeConverts UTC DateTime to specified user's timezone
GetFiscalEndDate()DateTimeGets the fiscal year end date from company preferences
GetFiscalQuarter(string qtr)QuarterGets fiscal quarter information including start and end dates
GetFiscalStartDate()DateTimeGets the fiscal year start date from company preferences
GetUserTimezoneOffset()TimeSpanGets the current user's timezone offset from UTC
SetTime(DateTime datetime, string time)DateTimeSets the time portion of a DateTime while preserving the date

ToRelativeTime(DateTime datetime,
bool isUtc = false, bool shortForm = false)

stringConverts DateTime to relative time string (e.g., "5 minutes ago")
ToUserRelativeTime(DateTime datetime)stringConverts UTC DateTime to relative time in user's timezone
ToUserTime(DateTime utcDateTime)DateTimeConverts UTC DateTime to user's local time

Methods

ConvertFromUTC

Converts UTC time to the current user's local timezone.

Description: This method takes a UTC DateTime value (typically retrieved from the database) and converts it to the user's local timezone based on their timezone preference. This is the primary method for displaying database timestamps to users.

Parameters:

  • utcTime (DateTime) - The UTC time to convert

Returns:

  • DateTime - The time converted to the user's local timezone

Usage Example:

// Retrieve a record from the database with a UTC timestamp
var account = Database.Retrieve<Account>("account-id");

// Convert the UTC CreatedDate to user's local time for display
DateTime userLocalTime = DateTimeUtil.ConvertFromUTC(account.CreatedDate);

// Display to user
string displayTime = userLocalTime.ToString("yyyy-MM-dd h:mm tt");
// Example output: "2025-10-30 2:30 PM" (if user is in EST)

Notes:

  • The method uses the current user's timezone preference from SystemInfo.User.Timezone
  • If the user has no timezone preference, it falls back to the company timezone from SystemInfo.Company.TimeZone
  • This method is functionally equivalent to the ToUserTime() extension method

ConvertToUTC

Converts a DateTime value from the user's local timezone to UTC for database storage.

Description: This method converts a DateTime value from the user's local timezone to UTC format, preparing it for storage in the database. This ensures all timestamps are stored consistently in UTC regardless of the user's location.

Parameters:

  • value (DateTime) - The local time to convert to UTC

Returns:

  • DateTime - The time converted to UTC

Usage Example:

// User enters a deadline date/time in their local timezone
DateTime userInputTime = new DateTime(2025, 12, 31, 23, 59, 59);

// Convert to UTC before saving to database
DateTime utcTime = DateTimeUtil.ConvertToUTC(userInputTime);

// Save to database
var opportunity = Database.Retrieve<Opportunity>("opp-id");
opportunity.CloseDate = utcTime;
Database.Update(opportunity);

Notes:

  • The method automatically handles DateTimeKind conversion
  • If the input DateTime has DateTimeKind.Utc, it is converted to DateTimeKind.Unspecified before conversion
  • The conversion uses the current user's timezone preference
Warnings: ⚠️ Always use this method before saving user-entered date/time values to the database to ensure proper UTC storage

ConvertToUserTime

Extension method that converts UTC DateTime to a specified user's timezone.

Description: This extension method allows you to convert a UTC DateTime to any specified timezone, not just the current user's timezone. This is useful when processing data for multiple users or when you need to display times in a specific timezone.

Parameters:

  • utcDateTime (DateTime) - The UTC time to convert
  • timezone (string) - The target timezone identifier (e.g., "Eastern Standard Time", "Pacific Standard Time")

Returns:

  • DateTime - The time converted to the specified timezone

Usage Example:

// Get a UTC timestamp from database
DateTime utcEventTime = DateTime.Parse("2025-10-30 18:00:00").ToUniversalTime();

// Convert to specific timezone for event display
string pacificTimeZone = "Pacific Standard Time";
DateTime pacificTime = utcEventTime.ConvertToUserTime(pacificTimeZone);

// Output: "2025-10-30 11:00:00 AM" (Pacific Time is UTC-7)

Alternative Usage with User Object:

// Convert time to a specific user's timezone
var targetUser = Database.Retrieve<User>("user-id");
DateTime userTime = utcDateTime.ConvertToUserTime(targetUser.Timezone);

Notes:

  • If the timezone parameter is null or empty, the method uses the company's default timezone
  • Standard Windows timezone identifiers should be used (e.g., "Eastern Standard Time", not "EST")

GetFiscalEndDate

Gets the fiscal year end date from company preferences.

Description: Returns the end date of the current fiscal year as configured in the company preferences. The fiscal year is configured in Setup > Company Preferences > Company Information tab.

Parameters: None

Returns:

  • DateTime - The fiscal year end date

Usage Example:

// Get the current fiscal year end date
DateTime fiscalEnd = DateTimeUtil.GetFiscalEndDate();

// Use in a query to filter records within the current fiscal year
var opportunities = Database.Query<Opportunity>()
    .Where(o => o.CloseDate <= fiscalEnd)
    .ToList();

// Display to user
string fiscalYearInfo = $"Current Fiscal Year ends on: {fiscalEnd:MMM dd, yyyy}";
// Example output: "Current Fiscal Year ends on: Dec 31, 2025"

Notes:

  • The fiscal year configuration is set at the company level in Setup > Company Preferences
  • Returns the value from SystemInfo.Company.FiscalEndDate
  • The fiscal year end date is determined by the fiscal start month and naming convention configured by administrators
💡 Related: See GetFiscalStartDate() for the beginning of the fiscal year

GetFiscalQuarter

Gets fiscal quarter information including start and end dates.

Description: Returns a Quarter object containing the start and end dates for a specified fiscal quarter. This method supports relative quarter references such as "lastquarter", "thisquarter", and "nextquarter".

Parameters:

  • qtr (string) - The quarter identifier. Supported values:
    • "lastquarter" - Previous fiscal quarter
    • "thisquarter" - Current fiscal quarter
    • "nextquarter" - Next fiscal quarter

Returns:

  • Quarter - Object containing Start and End DateTime properties

Usage Example:

// Get the current fiscal quarter information
Quarter currentQuarter = DateTimeUtil.GetFiscalQuarter("thisquarter");

// Access start and end dates
DateTime quarterStart = currentQuarter.Start;
DateTime quarterEnd = currentQuarter.End;

// Use in queries
var dealsThisQuarter = Database.Query<Deal>()
    .Where(d => d.CloseDate >= quarterStart && d.CloseDate <= quarterEnd)
    .ToList();

// Display quarter information
string quarterInfo = $"Q{GetQuarterNumber(quarterStart)} runs from {quarterStart:MMM dd} to {quarterEnd:MMM dd, yyyy}";
// Example output: "Q3 runs from Jul 01 to Sep 30, 2025"

Advanced Example - Comparing Quarters:

// Get last quarter's data
Quarter lastQuarter = DateTimeUtil.GetFiscalQuarter("lastquarter");
var lastQuarterRevenue = Database.Query<Opportunity>()
    .Where(o => o.CloseDate >= lastQuarter.Start && 
                o.CloseDate <= lastQuarter.End && 
                o.IsClosed)
    .Sum(o => o.Amount);

// Get this quarter's data
Quarter thisQuarter = DateTimeUtil.GetFiscalQuarter("thisquarter");
var thisQuarterRevenue = Database.Query<Opportunity>()
    .Where(o => o.CloseDate >= thisQuarter.Start && 
                o.CloseDate <= thisQuarter.End && 
                o.IsClosed)
    .Sum(o => o.Amount);

// Calculate quarter-over-quarter growth
decimal growth = ((thisQuarterRevenue - lastQuarterRevenue) / lastQuarterRevenue) * 100;

Notes:

  • Quarter boundaries are calculated based on the company's fiscal year start date configuration
  • The Quarter object provides direct access to Start and End properties as DateTime values
  • Quarter calculations respect the fiscal year configuration, which may differ from calendar quarters
💡 Configuration: Fiscal quarters are determined by the fiscal year start month configured in Setup > Company Preferences > Company Information

GetFiscalStartDate

Gets the fiscal year start date from company preferences.

Description: Returns the start date of the current fiscal year as configured in the company preferences. The fiscal year is configured in Setup > Company Preferences > Company Information tab.

Parameters: None

Returns:

  • DateTime - The fiscal year start date

Usage Example:

// Get the current fiscal year start date
DateTime fiscalStart = DateTimeUtil.GetFiscalStartDate();
DateTime fiscalEnd = DateTimeUtil.GetFiscalEndDate();

// Calculate fiscal year-to-date revenue
var ytdRevenue = Database.Query<Opportunity>()
    .Where(o => o.CloseDate >= fiscalStart && 
                o.CloseDate <= DateTime.UtcNow &&
                o.IsClosed)
    .Sum(o => o.Amount);

// Display fiscal year information
string fiscalYearInfo = $"Fiscal Year {fiscalStart.Year}: {fiscalStart:MMM dd, yyyy} - {fiscalEnd:MMM dd, yyyy}";
// Example output: "Fiscal Year 2025: Apr 01, 2025 - Mar 31, 2026"

Usage in Date Range Filtering:

// Create fiscal year filter for reports
DateTime fiscalStart = DateTimeUtil.GetFiscalStartDate();
DateTime fiscalEnd = DateTimeUtil.GetFiscalEndDate();

var opportunities = Database.Query<Opportunity>()
    .Where(o => o.CloseDate >= fiscalStart && o.CloseDate <= fiscalEnd)
    .OrderBy(o => o.CloseDate)
    .ToList();

Notes:

  • The fiscal year configuration is set at the company level in Setup > Company Preferences
  • Returns the value from SystemInfo.Company.FiscalStartDate
  • Common fiscal year start months include January (calendar year), April (UK), July (Australia), and October (US government)
💡 Related: See GetFiscalEndDate() for the end of the fiscal year and GetFiscalQuarter() for quarter-level date ranges

GetUserTimezoneOffset

Gets the current user's timezone offset from UTC as a TimeSpan.

Description: Returns the timezone offset between UTC and the user's local timezone, accounting for daylight saving time. This offset can be used for custom timezone calculations or display logic.

Parameters: None

Returns:

  • TimeSpan - The offset from UTC (positive for timezones ahead of UTC, negative for timezones behind)

Usage Example:

// Get the user's timezone offset
TimeSpan offset = DateTimeUtil.GetUserTimezoneOffset();

// Display offset information
int hours = offset.Hours;
string offsetDisplay = $"Your timezone is UTC{(hours >= 0 ? "+" : "")}{hours}";
// Example output: "Your timezone is UTC-5" (Eastern Time)
// Example output: "Your timezone is UTC+1" (Central European Time)

Advanced Usage - Manual Time Calculations:

// Get user's offset
TimeSpan userOffset = DateTimeUtil.GetUserTimezoneOffset();

// Get current UTC time
DateTime utcNow = DateTime.UtcNow;

// Calculate user's local time manually
DateTime userLocalTime = utcNow.Add(userOffset);

// Display time with offset
string display = $"Current time: {userLocalTime:h:mm tt} (UTC{(userOffset.Hours >= 0 ? "+" : "")}{userOffset.Hours})";
// Example output: "Current time: 2:30 PM (UTC-5)"

Usage in API Responses:

public ActionResponse GetTimeInfo()
{
    var offset = DateTimeUtil.GetUserTimezoneOffset();
    
    return Json(new
    {
        utcTime = DateTime.UtcNow,
        userOffset = offset.TotalHours,
        offsetDisplay = $"UTC{(offset.Hours >= 0 ? "+" : "")}{offset.Hours}"
    });
}

Notes:

  • The method automatically considers daylight saving time adjustments
  • Offset is calculated based on the current user's timezone preference or company default
  • The TimeSpan includes both hours and minutes components (e.g., some timezones have 30-minute offsets like India Standard Time at UTC+5:30)
  • If the user has no timezone configured, the company's default timezone is used
💡 Tip: For most display scenarios, use ConvertFromUTC() or ToUserTime() instead of manually calculating with offsets

SetTime

Extension method that sets the time portion of a DateTime while preserving the date.

Description: This extension method allows you to modify the time component of a DateTime object while keeping the date portion unchanged. This is useful when you need to set specific times (like start of day, end of day, or specific meeting times) on existing dates.

Parameters:

  • datetime (DateTime) - The DateTime to modify
  • time (string) - The time string to set (must be parseable by DateTime.Parse)

Returns:

  • DateTime - A new DateTime with the specified time and original date

Usage Example:

// Set a specific time on today's date
DateTime today = DateTime.Now.Date;
DateTime startOfBusiness = today.SetTime("9:00 AM");
DateTime endOfBusiness = today.SetTime("5:00 PM");

// Example: Create a same-day event
var meeting = new Event
{
    Subject = "Team Standup",
    StartDate = today.SetTime("9:30 AM"),
    EndDate = today.SetTime("10:00 AM")
};
Database.Insert(meeting);

Setting End of Day:

// Get the last moment of a specific date
DateTime deadline = DateTime.Parse("2025-12-31");
DateTime endOfDay = deadline.SetTime("11:59:59 PM");

// Use in queries
var dueTodayItems = Database.Query<Task>()
    .Where(t => t.DueDate <= endOfDay)
    .ToList();

Working with Date Ranges:

// Create a date range for a full day
DateTime selectedDate = DateTime.Parse("2025-10-30");
DateTime dayStart = selectedDate.SetTime("12:00 AM"); // Start of day
DateTime dayEnd = selectedDate.SetTime("11:59:59 PM"); // End of day

// Query records within the day
var activities = Database.Query<Activity>()
    .Where(a => a.ActivityDate >= dayStart && a.ActivityDate <= dayEnd)
    .ToList();

24-Hour Format:

// Using 24-hour time format
DateTime appointment = DateTime.Today.SetTime("14:30"); // 2:30 PM
DateTime lateNight = DateTime.Today.SetTime("23:45");   // 11:45 PM

Notes:

  • The time string must be in a format that DateTime.Parse can interpret
  • Supported formats include: "9:00 AM", "9:00:00 AM", "14:30", "14:30:00"
  • The original DateTime's DateTimeKind is preserved in the returned DateTime
  • This method does not perform timezone conversions; it only modifies the time component
Warnings: ⚠️ The time string must be valid and parseable. Invalid time strings will throw a FormatException
⚠️ Be careful with timezone implications. If working with UTC times, ensure your time string represents the intended UTC time, not local time

ToRelativeTime

Extension method that converts a DateTime to a human-readable relative time string.

Description: Converts a DateTime value into a relative time string like "5 minutes ago", "2 hours ago", or "3 days ago". This method provides user-friendly time displays for activity feeds, notifications, and timestamps where exact times are less important than relative recency.

Parameters:

  • datetime (DateTime) - The DateTime to convert to relative time
  • isUtc (bool, optional) - Indicates whether the input DateTime is in UTC. Default: false
  • shortForm (bool, optional) - Whether to use abbreviated format (e.g., "5m" instead of "5 minutes ago"). Default: false

Returns:

  • string - The relative time string (automatically localized based on user's culture)

Usage Example - Basic:

// Display when a record was created
DateTime createdTime = DateTime.Now.AddMinutes(-30);
string relativeTime = createdTime.ToRelativeTime();
// Output: "30 minutes ago"

// Display in UI
string message = $"Post created {relativeTime}";
// Output: "Post created 30 minutes ago"

Usage Example - Short Form:

// Use short form for compact displays
DateTime lastActive = DateTime.Now.AddHours(-2);
string shortTime = lastActive.ToRelativeTime(shortForm: true);
// Output: "2h"

// Useful for activity indicators
string status = $"Active {shortTime}";
// Output: "Active 2h"

Usage Example - With UTC Times:

// When working with UTC database times
DateTime utcCreatedDate = account.CreatedDate; // Already in UTC
string relativeTime = utcCreatedDate.ToRelativeTime(isUtc: true);
// Output: "5 days ago" (converted to user's timezone for calculation)

Common Time Ranges:

Actual Time DifferenceLong FormShort Form
< 1 minute"just now""1s" - "59s"
1 minute"one minute ago""1m"
2-44 minutes"X minutes ago""Xm"
45-89 minutes"an hour ago""1h"
90 minutes - 23 hours"X hours ago""Xh"
24-47 hours"X hours ago""Xh"
2-29 days"X days ago""Xd"
30-364 days"X months ago""X months"
365+ days"X years ago""Xy"

Advanced Example - Activity Feed:

// Build an activity feed with relative times
var recentActivities = Database.Query<Activity>()
    .Where(a => a.OwnerId == SystemInfo.UserId)
    .OrderByDescending(a => a.CreatedDate)
    .Take(10)
    .ToList();

foreach (var activity in recentActivities)
{
    // Convert UTC database time to relative time
    string timeAgo = activity.CreatedDate.ToRelativeTime(isUtc: true);
    string message = $"{activity.Subject} - {timeAgo}";
    // Example output: "Meeting with client - 2 hours ago"
}

Future Times:

// The method also handles future times
DateTime futureEvent = DateTime.Now.AddHours(2);
string timeUntil = futureEvent.ToRelativeTime();
// Output: "2 hours left" (for future dates)

DateTime tomorrow = DateTime.Now.AddDays(1);
string daysLeft = tomorrow.ToRelativeTime();
// Output: "1 day left"

Notes:

  • Strings are automatically localized based on the user's culture settings (CultureInfo.CurrentUICulture)
  • The method supporting multiple languages
  • When isUtc is true, the method converts to user's local timezone before calculating relative time
  • For times less than 1 second, displays "just now"
  • The method intelligently switches between seconds, minutes, hours, days, months, and years based on the time difference
💡 Best Practice: Use relative times for activity feeds, notifications, and recent activity displays. Use absolute times for important deadlines, scheduled events, and historical records.

ToUserRelativeTime

Extension method that converts a UTC DateTime to a relative time string in the user's timezone.

Description: Convenience method that combines timezone conversion and relative time formatting in a single call. It converts a UTC DateTime to the user's local timezone and then formats it as a relative time string (e.g., "5 minutes ago").

Parameters:

  • datetime (DateTime) - The UTC DateTime to convert (should be in UTC)

Returns:

  • string - The relative time string in the user's timezone (automatically localized)

Usage Example:

// Retrieve a record with UTC timestamp from database
var comment = Database.Retrieve<Comment>("comment-id");

// Convert UTC CreatedDate to relative time in user's timezone
string timeAgo = comment.CreatedDate.ToUserRelativeTime();
// Output: "5 minutes ago" (in user's local timezone)

// Display in UI
string display = $"Comment posted {timeAgo}";
// Output: "Comment posted 5 minutes ago"

Usage in List Views:

// Display relative times for a list of records
var notifications = Database.Query<Notification>()
    .Where(n => n.RecipientId == SystemInfo.UserId && !n.IsRead)
    .OrderByDescending(n => n.CreatedDate)
    .ToList();

foreach (var notification in notifications)
{
    // Each CreatedDate is in UTC from database
    string timeAgo = notification.CreatedDate.ToUserRelativeTime();
    string message = $"{notification.Title} - {timeAgo}";
    // Example output: "New message received - 10 minutes ago"
}

Comparison with ToRelativeTime:

// These two approaches are equivalent:

// Approach 1: Two-step process
DateTime userLocalTime = DateTimeUtil.ConvertFromUTC(utcDateTime);
string relativeTime1 = userLocalTime.ToRelativeTime();

// Approach 2: Single method call (preferred)
string relativeTime2 = utcDateTime.ToUserRelativeTime();

// Both produce the same result

Activity Timeline Example:

public class ActivityViewModel
{
    public string Subject { get; set; }
    public string TimeAgo { get; set; }
}

// Build activity timeline with relative times
var activities = Database.Query<Activity>()
    .Where(a => a.AccountId == accountId)
    .OrderByDescending(a => a.ActivityDate)
    .Take(20)
    .ToList()
    .Select(a => new ActivityViewModel
    {
        Subject = a.Subject,
        TimeAgo = a.ActivityDate.ToUserRelativeTime() // UTC to relative time
    })
    .ToList();

// Results ready for display in user's timezone
// Example: [{ Subject: "Phone call", TimeAgo: "2 hours ago" }, ...]

Notes:

  • This method assumes the input DateTime is in UTC (as stored in the database)
  • Internally calls ConvertFromUTC() followed by ToRelativeTime()
  • Automatically localized based on user's culture settings
  • Ideal for displaying database timestamps in a user-friendly format
💡 When to Use: This is the preferred method when working with UTC database timestamps that need to be displayed as relative times. It's more concise and clearer than manually converting and formatting.

ToUserTime

Extension method that converts a UTC DateTime to the current user's local time.

Description: Converts a UTC DateTime value to the user's local timezone. This is an extension method version of ConvertFromUTC(), providing a more fluent syntax for timezone conversions.

Parameters:

  • utcDateTime (DateTime) - The UTC DateTime to convert

Returns:

  • DateTime - The DateTime converted to the user's local timezone

Usage Example:

// Retrieve record with UTC timestamp from database
var opportunity = Database.Retrieve<Opportunity>("opp-id");

// Convert UTC CloseDate to user's local time using extension method
DateTime userLocalCloseDate = opportunity.CloseDate.ToUserTime();

// Display to user
string displayDate = userLocalCloseDate.ToString("MMM dd, yyyy h:mm tt");
// Example output: "Dec 31, 2025 11:59 PM" (in user's timezone)

Fluent Chaining:

// Extension method allows for fluent syntax
string formattedDate = account.CreatedDate
    .ToUserTime()
    .ToString("yyyy-MM-dd");

// Equivalent to:
DateTime userTime = DateTimeUtil.ConvertFromUTC(account.CreatedDate);
string formattedDate = userTime.ToString("yyyy-MM-dd");

Usage in LINQ Queries:

// Convert and format dates in LINQ projections
var upcomingEvents = Database.Query<Event>()
    .Where(e => e.StartDate > DateTime.UtcNow)
    .OrderBy(e => e.StartDate)
    .ToList()
    .Select(e => new
    {
        Title = e.Subject,
        LocalStartTime = e.StartDate.ToUserTime(), // Convert to user time
        FormattedTime = e.StartDate.ToUserTime().ToString("g")
    })
    .ToList();

Null Handling:

// Safe handling of nullable DateTime fields
DateTime? lastModifiedUtc = record.ModifiedDate;

DateTime? userLocalTime = lastModifiedUtc?.ToUserTime();

// Display with null check
string displayTime = userLocalTime?.ToString("g") ?? "Never modified";

Comparison: ToUserTime vs ConvertFromUTC:

// Both methods are functionally equivalent:

// Method 1: Static method
DateTime result1 = DateTimeUtil.ConvertFromUTC(utcDateTime);

// Method 2: Extension method (more fluent)
DateTime result2 = utcDateTime.ToUserTime();

// Choose based on coding style preference

Notes:

  • This method is functionally identical to ConvertFromUTC() but provides extension method syntax
  • Uses the current user's timezone preference from SystemInfo.User.Timezone
  • Falls back to company timezone if user has no timezone preference
  • Input DateTime should be in UTC (as stored in the database)
💡 Coding Style: Use ToUserTime() for a more fluent, readable coding style, especially when chaining methods. Use ConvertFromUTC() when you prefer explicit static method calls.

Common Usage Patterns

Pattern 1: Displaying Database Times to Users

When retrieving date/time values from the database, they are stored in UTC and need to be converted to the user's timezone for display.

public ActionResponse ViewOpportunity(string id)
{
    // Retrieve opportunity from database (dates are in UTC)
    var opportunity = Database.Retrieve<Opportunity>(id);
    
    // Convert UTC times to user's local timezone for display
    DateTime localCloseDate = opportunity.CloseDate.ToUserTime();
    DateTime localCreatedDate = opportunity.CreatedDate.ToUserTime();
    DateTime localModifiedDate = opportunity.ModifiedDate.ToUserTime();
    
    // Create view model with converted times
    var viewModel = new OpportunityViewModel
    {
        Name = opportunity.Name,
        Amount = opportunity.Amount,
        CloseDate = localCloseDate.ToString("MMM dd, yyyy"),
        CreatedDate = localCreatedDate.ToString("g"),
        ModifiedDate = localModifiedDate.ToString("g")
    };
    
    return View(viewModel);
}

Alternative using relative time for timestamps:

// For activity timestamps, use relative time
var viewModel = new OpportunityViewModel
{
    Name = opportunity.Name,
    CreatedTime = opportunity.CreatedDate.ToUserRelativeTime(), // "5 days ago"
    LastModified = opportunity.ModifiedDate.ToUserRelativeTime() // "2 hours ago"
};

Pattern 2: Saving User Input to Database

When accepting date/time input from users, convert their local time to UTC before saving to the database.

[HttpPost]
public ActionResponse CreateEvent(string subject, DateTime startDate, DateTime endDate)
{
    // User entered dates in their local timezone
    // Convert to UTC before saving to database
    DateTime utcStartDate = DateTimeUtil.ConvertToUTC(startDate);
    DateTime utcEndDate = DateTimeUtil.ConvertToUTC(endDate);
    
    // Create and save event with UTC times
    var newEvent = new Event
    {
        Subject = subject,
        StartDate = utcStartDate,
        EndDate = utcEndDate,
        OwnerId = SystemInfo.UserId
    };
    
    Database.Insert(newEvent);
    
    return Json(new { success = true, id = newEvent.Id });
}

Setting specific times on dates:

[HttpPost]
public ActionResponse CreateTask(string subject, DateTime dueDate)
{
    // User selected a date, set end of day as deadline
    DateTime endOfDay = dueDate.SetTime("11:59:59 PM");
    
    // Convert to UTC for storage
    DateTime utcDueDate = DateTimeUtil.ConvertToUTC(endOfDay);
    
    var task = new Task
    {
        Subject = subject,
        DueDate = utcDueDate,
        OwnerId = SystemInfo.UserId
    };
    
    Database.Insert(task);
    
    return Json(new { success = true });
}

Pattern 3: Working with Relative Times

Display user-friendly relative time strings for activity feeds, notifications, and recent activity lists.

public ActionResponse GetActivityFeed()
{
    // Get recent activities (dates in UTC from database)
    var activities = Database.Query<Activity>()
        .Where(a => a.OwnerId == SystemInfo.UserId)
        .OrderByDescending(a => a.CreatedDate)
        .Take(20)
        .ToList();
    
    // Convert to view models with relative times
    var feedItems = activities.Select(a => new
    {
        Id = a.Id,
        Subject = a.Subject,
        Description = a.Description,
        TimeAgo = a.CreatedDate.ToUserRelativeTime(), // "5 minutes ago"
        TimeAgoShort = a.CreatedDate.ToRelativeTime(isUtc: true, shortForm: true) // "5m"
    }).ToList();
    
    return Json(feedItems);
}

Notification system with relative times:

public class NotificationService
{
    public List<NotificationViewModel> GetUnreadNotifications(string userId)
    {
        var notifications = Database.Query<Notification>()
            .Where(n => n.RecipientId == userId && !n.IsRead)
            .OrderByDescending(n => n.CreatedDate)
            .ToList();
        
        return notifications.Select(n => new NotificationViewModel
        {
            Id = n.Id,
            Title = n.Title,
            Message = n.Message,
            TimeDisplay = GetSmartTimeDisplay(n.CreatedDate),
            Icon = n.Type
        }).ToList();
    }
    
    private string GetSmartTimeDisplay(DateTime utcCreatedDate)
    {
        // Show relative time for recent notifications
        var localTime = utcCreatedDate.ToUserTime();
        var hoursSince = (DateTime.Now - localTime).TotalHours;
        
        if (hoursSince < 24)
        {
            // Less than 24 hours: show relative time
            return utcCreatedDate.ToUserRelativeTime(); // "5 minutes ago"
        }
        else
        {
            // Older: show absolute date
            return localTime.ToString("MMM dd, yyyy"); // "Oct 15, 2025"
        }
    }
}

Pattern 4: Working with Fiscal Periods

Calculate fiscal year and quarter boundaries for reporting and filtering.

public ActionResponse GetFiscalYearSummary()
{
    // Get fiscal year boundaries
    DateTime fiscalStart = DateTimeUtil.GetFiscalStartDate();
    DateTime fiscalEnd = DateTimeUtil.GetFiscalEndDate();
    
    // Calculate year-to-date revenue
    var ytdOpportunities = Database.Query<Opportunity>()
        .Where(o => o.CloseDate >= fiscalStart && 
                    o.CloseDate <= DateTime.UtcNow &&
                    o.IsClosed)
        .ToList();
    
    decimal ytdRevenue = ytdOpportunities.Sum(o => o.Amount);
    
    // Calculate full year pipeline
    var fullYearOpportunities = Database.Query<Opportunity>()
        .Where(o => o.CloseDate >= fiscalStart && 
                    o.CloseDate <= fiscalEnd)
        .ToList();
    
    decimal fullYearPipeline = fullYearOpportunities.Sum(o => o.Amount);
    
    return Json(new
    {
        FiscalYearStart = fiscalStart.ToString("MMM dd, yyyy"),
        FiscalYearEnd = fiscalEnd.ToString("MMM dd, yyyy"),
        YTDRevenue = ytdRevenue,
        FullYearPipeline = fullYearPipeline,
        PercentComplete = (decimal)DateTime.UtcNow.DayOfYear / 365 * 100
    });
}

Quarterly reporting with comparisons:

public ActionResponse GetQuarterlyReport()
{
    // Get current and previous quarter information
    Quarter thisQuarter = DateTimeUtil.GetFiscalQuarter("thisquarter");
    Quarter lastQuarter = DateTimeUtil.GetFiscalQuarter("lastquarter");
    
    // Calculate revenue for each quarter
    decimal thisQuarterRevenue = Database.Query<Opportunity>()
        .Where(o => o.CloseDate >= thisQuarter.Start && 
                    o.CloseDate <= thisQuarter.End &&
                    o.IsClosed)
        .Sum(o => o.Amount);
    
    decimal lastQuarterRevenue = Database.Query<Opportunity>()
        .Where(o => o.CloseDate >= lastQuarter.Start && 
                    o.CloseDate <= lastQuarter.End &&
                    o.IsClosed)
        .Sum(o => o.Amount);
    
    // Calculate growth
    decimal growth = lastQuarterRevenue > 0 
        ? ((thisQuarterRevenue - lastQuarterRevenue) / lastQuarterRevenue) * 100 
        : 0;
    
    return Json(new
    {
        CurrentQuarter = new
        {
            Start = thisQuarter.Start.ToString("MMM dd, yyyy"),
            End = thisQuarter.End.ToString("MMM dd, yyyy"),
            Revenue = thisQuarterRevenue
        },
        PreviousQuarter = new
        {
            Start = lastQuarter.Start.ToString("MMM dd, yyyy"),
            End = lastQuarter.End.ToString("MMM dd, yyyy"),
            Revenue = lastQuarterRevenue
        },
        QuarterOverQuarterGrowth = $"{growth:F2}%"
    });
}

Fiscal quarter-based filtering:

public ActionResponse GetQuarterlyPipeline(string quarter)
{
    // Get the specified quarter's date range
    Quarter selectedQuarter = DateTimeUtil.GetFiscalQuarter(quarter);
    
    // Query opportunities within the quarter
    var opportunities = Database.Query<Opportunity>()
        .Where(o => o.CloseDate >= selectedQuarter.Start && 
                    o.CloseDate <= selectedQuarter.End)
        .OrderBy(o => o.CloseDate)
        .ToList();
    
    // Group by month within quarter
    var monthlyBreakdown = opportunities
        .GroupBy(o => new { o.CloseDate.Year, o.CloseDate.Month })
        .Select(g => new
        {
            Month = new DateTime(g.Key.Year, g.Key.Month, 1).ToString("MMM yyyy"),
            Count = g.Count(),
            TotalAmount = g.Sum(o => o.Amount)
        })
        .ToList();
    
    return Json(new
    {
        QuarterStart = selectedQuarter.Start.ToString("MMM dd, yyyy"),
        QuarterEnd = selectedQuarter.End.ToString("MMM dd, yyyy"),
        TotalOpportunities = opportunities.Count,
        TotalValue = opportunities.Sum(o => o.Amount),
        MonthlyBreakdown = monthlyBreakdown
    });
}

Pattern 5: Modifying Time Components

Use SetTime() to set specific times on dates while preserving the date portion.

public ActionResponse CreateDaySchedule(DateTime date)
{
    // Define business hours
    DateTime dayStart = date.SetTime("9:00 AM");
    DateTime lunchStart = date.SetTime("12:00 PM");
    DateTime lunchEnd = date.SetTime("1:00 PM");
    DateTime dayEnd = date.SetTime("5:00 PM");
    
    // Create scheduled events
    var morningMeeting = new Event
    {
        Subject = "Team Standup",
        StartDate = DateTimeUtil.ConvertToUTC(date.SetTime("9:30 AM")),
        EndDate = DateTimeUtil.ConvertToUTC(date.SetTime("10:00 AM")),
        OwnerId = SystemInfo.UserId
    };
    
    var clientCall = new Event
    {
        Subject = "Client Review",
        StartDate = DateTimeUtil.ConvertToUTC(date.SetTime("2:00 PM")),
        EndDate = DateTimeUtil.ConvertToUTC(date.SetTime("3:00 PM")),
        OwnerId = SystemInfo.UserId
    };
    
    // Save events
    Database.Insert(morningMeeting);
    Database.Insert(clientCall);
    
    return Json(new { success = true });
}

Creating date ranges for queries:

public ActionResponse GetDailyActivity(DateTime selectedDate)
{
    // Create full-day range using SetTime
    DateTime dayStart = selectedDate.SetTime("12:00 AM");
    DateTime dayEnd = selectedDate.SetTime("11:59:59 PM");
    
    // Convert to UTC for database query
    DateTime utcStart = DateTimeUtil.ConvertToUTC(dayStart);
    DateTime utcEnd = DateTimeUtil.ConvertToUTC(dayEnd);
    
    // Query all activities for the day
    var activities = Database.Query<Activity>()
        .Where(a => a.ActivityDate >= utcStart && 
                    a.ActivityDate <= utcEnd &&
                    a.OwnerId == SystemInfo.UserId)
        .OrderBy(a => a.ActivityDate)
        .ToList();
    
    // Convert back to user time for display
    var displayActivities = activities.Select(a => new
    {
        Subject = a.Subject,
        Time = a.ActivityDate.ToUserTime().ToString("h:mm tt"),
        Description = a.Description
    }).ToList();
    
    return Json(displayActivities);
}

Scheduling with specific times:

public ActionResponse ScheduleRecurringMeeting(DateTime startDate, int occurrences)
{
    // Set specific meeting time
    DateTime meetingTime = startDate.SetTime("10:00 AM");
    
    // Create recurring meetings
    for (int i = 0; i < occurrences; i++)
    {
        DateTime occurrenceDate = meetingTime.AddDays(i * 7); // Weekly
        DateTime occurrenceEnd = occurrenceDate.AddHours(1); // 1-hour meeting
        
        var meeting = new Event
        {
            Subject = "Weekly Team Meeting",
            StartDate = DateTimeUtil.ConvertToUTC(occurrenceDate),
            EndDate = DateTimeUtil.ConvertToUTC(occurrenceEnd),
            OwnerId = SystemInfo.UserId
        };
        
        Database.Insert(meeting);
    }
    
    return Json(new { success = true, meetingsCreated = occurrences });
}

Method Comparison Guide

When to Use Which Conversion Method?

ScenarioRecommended MethodReason
Converting UTC from database for displayConvertFromUTC() or ToUserTime()Both are equivalent; ToUserTime() provides fluent syntax as extension method
Converting with specific timezoneConvertToUserTime(timezone)Allows you to specify a custom timezone parameter
Converting user input to UTC for storageConvertToUTC()Properly handles DateTimeKind and converts from user's timezone to UTC
Display relative time from UTC database valueToUserRelativeTime()Combines timezone conversion and relative formatting in one call
Display relative time (already converted to local)ToRelativeTime()Use when DateTime is already in user's timezone
Display relative time in short formatToRelativeTime(shortForm: true)Provides abbreviated format like "5m", "2h", "3d"

Conversion Method Decision Tree

Do you have a UTC database value?
│
├─ YES → Need to display it?
│        │
│        ├─ As absolute time → Use ConvertFromUTC() or ToUserTime()
│        │
│        └─ As relative time → Use ToUserRelativeTime()
│
└─ NO → Have user's local time?
         │
         └─ Need to save to database? → Use ConvertToUTC()

Relative Time Method Comparison

MethodInput TypeOutputUse Case
ToRelativeTime()Local DateTimeRelative stringDateTime already in user's timezone
ToRelativeTime(isUtc: true)UTC DateTimeRelative stringUTC DateTime, need timezone conversion
ToUserRelativeTime()UTC DateTimeRelative stringPreferred for UTC database values
ToRelativeTime(shortForm: true)Any DateTimeShort relative string Compact displays (e.g., "5m", "2h")

Best Practices

1. Always Store in UTC

Store all date and time values in UTC format in the database. Never store local times.

// ✅ CORRECT
DateTime userInput = GetUserDateInput();
DateTime utcValue = DateTimeUtil.ConvertToUTC(userInput);
record.DueDate = utcValue;
Database.Update(record);

// ❌ INCORRECT - storing local time
record.DueDate = userInput; // This is local time!
Database.Update(record);

2. Convert for Display

Always convert UTC database values to user's local time before displaying.

// ✅ CORRECT
var record = Database.Retrieve<Opportunity>(id);
DateTime displayTime = record.CloseDate.ToUserTime();
ViewBag.CloseDate = displayTime.ToString("g");

// ❌ INCORRECT - displaying UTC time
ViewBag.CloseDate = record.CloseDate.ToString("g"); // User sees UTC time!

3. Let UI Components Handle Standard Cases

For standard form input and display, use Magentrix UI components (FieldDateTime, InputField) which handle conversions automatically.

// ✅ UI components handle timezone conversion automatically
<aspx:InputField runat='server' value='{!Model.DueDate}' />
// Component automatically:
// - Displays UTC database value in user's local time
// - Converts user input back to UTC on save

// ⚠️ Only use DateTimeUtil for custom business logic or programmatic processing

4. Check DateTimeKind

Be aware of the DateTimeKind property, especially when working with ConvertToUTC().

// ✅ CORRECT - ConvertToUTC handles DateTimeKind
DateTime userInput = DateTime.Parse("2025-10-30 10:00:00"); // Kind: Unspecified
DateTime utcValue = DateTimeUtil.ConvertToUTC(userInput); // Properly converts

// ⚠️ Be careful with DateTime.UtcNow
DateTime utcNow = DateTime.UtcNow; // Kind: UTC
// ConvertToUTC automatically adjusts if Kind is UTC

5. Use Relative Times Appropriately

Relative times are great for recent activity but not suitable for important dates.

// ✅ GOOD - Relative time for activity feed
string activityTime = activity.CreatedDate.ToUserRelativeTime();
// Output: "5 minutes ago"

// ❌ BAD - Relative time for important deadline
string deadline = opportunity.CloseDate.ToUserRelativeTime();
// Output: "3 days ago" - User can't tell the exact date!

// ✅ BETTER - Absolute time for deadlines
string deadline = opportunity.CloseDate.ToUserTime().ToString("MMM dd, yyyy");
// Output: "Oct 30, 2025"

When to Use Relative vs Absolute:

Use Relative Time ForUse Absolute Time For
Activity feedsDeadlines and due dates
Notification timestampsScheduled events
"Last updated" indicatorsContract dates
Comment timestampsFiscal year boundaries
Recent activityHistorical records

6. Cache Timezone Information

If performing multiple timezone operations in the same request, cache the timezone offset.

// ✅ EFFICIENT - Cache offset for multiple operations
TimeSpan userOffset = DateTimeUtil.GetUserTimezoneOffset();

foreach (var record in records)
{
    // Use cached offset instead of calling GetUserTimezoneOffset repeatedly
    DateTime localTime = record.CreatedDate.Add(userOffset);
    // ... process record
}

// ⚠️ INEFFICIENT - Calling method repeatedly
foreach (var record in records)
{
    TimeSpan offset = DateTimeUtil.GetUserTimezoneOffset(); // Repeated calls
    DateTime localTime = record.CreatedDate.Add(offset);
}

// ✅ EVEN BETTER - Use ToUserTime() which is optimized
foreach (var record in records)
{
    DateTime localTime = record.CreatedDate.ToUserTime();
    // ... process record
}

7. Handle Null DateTime Values

Always check for null DateTime values before conversion.

// ✅ CORRECT - Null check before conversion
DateTime? lastModified = record.ModifiedDate;
string displayTime = lastModified.HasValue 
    ? lastModified.Value.ToUserTime().ToString("g")
    : "Never modified";

// ✅ ALTERNATIVE - Using null-conditional operator
string displayTime = record.ModifiedDate?.ToUserTime().ToString("g") ?? "Never modified";

// ❌ INCORRECT - May throw exception
string displayTime = record.ModifiedDate.ToUserTime().ToString("g"); // Null reference!

8. Be Consistent with Date Formats

Use consistent date/time formats throughout your application.

// ✅ GOOD - Consistent formatting
string shortDate = dateTime.ToString("MMM dd, yyyy"); // Oct 30, 2025
string longDate = dateTime.ToString("MMMM dd, yyyy"); // October 30, 2025
string dateTime = dateTime.ToString("g"); // 10/30/2025 2:30 PM
string iso = dateTime.ToString("yyyy-MM-dd"); // 2025-10-30

// Consider creating format constants
public static class DateFormats
{
    public const string ShortDate = "MMM dd, yyyy";
    public const string LongDate = "MMMM dd, yyyy";
    public const string DateTime = "g";
    public const string ISO = "yyyy-MM-dd";
}

9. Fiscal Period Calculations

When working with fiscal periods, always use the DateTimeUtil methods rather than hardcoding dates.

// ✅ CORRECT - Use fiscal methods
DateTime fiscalStart = DateTimeUtil.GetFiscalStartDate();
DateTime fiscalEnd = DateTimeUtil.GetFiscalEndDate();

var ytdRevenue = Database.Query<Opportunity>()
    .Where(o => o.CloseDate >= fiscalStart && o.CloseDate <= fiscalEnd)
    .Sum(o => o.Amount);

// ❌ INCORRECT - Hardcoded dates
var ytdRevenue = Database.Query<Opportunity>()
    .Where(o => o.CloseDate >= new DateTime(2025, 1, 1)) // Assumes calendar year!
    .Sum(o => o.Amount);

10. Testing with Different Timezones

When testing, verify behavior across different timezones.

// Test helper method
public void TestTimezoneBehavior()
{
    // Create test data in UTC
    DateTime utcTime = new DateTime(2025, 10, 30, 18, 0, 0, DateTimeKind.Utc);
    
    // Simulate different user timezones
    string[] testTimezones = new[]
    {
        "Eastern Standard Time",   // UTC-5
        "Pacific Standard Time",   // UTC-8
        "GMT Standard Time",       // UTC+0
        "Tokyo Standard Time"      // UTC+9
    };
    
    foreach (var timezone in testTimezones)
    {
        DateTime localTime = utcTime.ConvertToUserTime(timezone);
        Console.WriteLine($"{timezone}: {localTime:g}");
    }
}

Troubleshooting

Problem: Times Showing Incorrectly

Symptoms:

  • Displayed times don't match user's expected timezone
  • Times are off by several hours
  • Dates show wrong day

Solutions:

  1. Verify DateTimeKind:
// Check the DateTimeKind of your DateTime
SystemInfo.Debug($"DateTime Kind: {myDateTime.Kind}");
// Should be Utc for database values, Unspecified for user input

// If incorrect, specify the kind:
DateTime utcTime = DateTime.SpecifyKind(myDateTime, DateTimeKind.Utc);
  1. Verify User Timezone Configuration:
// Check user's timezone setting
string userTimezone = SystemInfo.User.Timezone;
Console.WriteLine($"User Timezone: {userTimezone}");

// Check company default timezone
string companyTimezone = SystemInfo.Company.TimeZone;
SystemInfo.Debug($"Company Timezone: {companyTimezone}");
  1. Verify Conversion Direction:
// ✅ CORRECT - Database to display
DateTime displayTime = databaseUtcTime.ToUserTime();

// ❌ WRONG DIRECTION - Display to database
DateTime wrongTime = userInputTime.ToUserTime(); // Makes it MORE wrong!

// ✅ CORRECT - User input to database
DateTime dbTime = userInputTime.ConvertToUTC();

Problem: Relative Times Not Localized

Symptoms:

  • Relative time strings appear in wrong language
  • Format doesn't match user's culture
  • "ago" text not translated

Solutions:

  1. Check Culture Settings:
// Verify current culture
var currentCulture = System.Globalization.CultureInfo.CurrentCulture;
var currentUICulture = System.Globalization.CultureInfo.CurrentUICulture;

SystemInfo.Debug($"Culture: {currentCulture.Name}");
SystemInfo.Debug($"UI Culture: {currentUICulture.Name}");
  1. Verify WebResources are Available:
// The ToRelativeTime() method uses WebResources for localization
// Ensure resource files are properly deployed for your culture
  1. Use SystemInfo for Culture Information:
var userCulture = SystemInfo.UICulture();
SystemInfo.Debug($"User Culture: {userCulture.DisplayName}");

Problem: Fiscal Dates Returning Unexpected Values

Symptoms:

  • Fiscal start/end dates don't match expectations
  • Quarter boundaries seem incorrect
  • Fiscal year calculations produce wrong results

Solutions:

  1. Verify Company Fiscal Year Configuration:

    • Navigate to Setup > Company Preferences
    • Click Edit > Company Information tab
    • Verify "Fiscal Year Start Month" setting
    • Verify "Fiscal Year Name Based On" setting
  2. Check Fiscal Year Boundaries:

// Debug fiscal year configuration
DateTime fiscalStart = DateTimeUtil.GetFiscalStartDate();
DateTime fiscalEnd = DateTimeUtil.GetFiscalEndDate();

SystemInfo.Debug($"Fiscal Year: {fiscalStart:MMM dd, yyyy} to {fiscalEnd:MMM dd, yyyy}");

// Verify quarters align with fiscal year
Quarter q1 = DateTimeUtil.GetFiscalQuarter("thisquarter");
SystemInfo.Debug($"Current Quarter: {q1.Start:MMM dd} to {q1.End:MMM dd}");
  1. Verify Quarter String Parameters:
// ✅ CORRECT - Valid quarter strings
var thisQ = DateTimeUtil.GetFiscalQuarter("thisquarter");
var lastQ = DateTimeUtil.GetFiscalQuarter("lastquarter");
var nextQ = DateTimeUtil.GetFiscalQuarter("nextquarter");

// ❌ INCORRECT - Invalid quarter strings
var invalid = DateTimeUtil.GetFiscalQuarter("Q1"); // Not supported
var invalid2 = DateTimeUtil.GetFiscalQuarter("first quarter"); // Not supported

Problem: SetTime Throws Exception

Symptoms:

  • FormatException when calling SetTime()
  • "String was not recognized as a valid DateTime"

Solutions:

  1. Use Valid Time Format:
// ✅ CORRECT - Valid time formats
var time1 = date.SetTime("9:00 AM");
var time2 = date.SetTime("9:00:00 AM");
var time3 = date.SetTime("14:30"); // 24-hour format
var time4 = date.SetTime("14:30:00");

// ❌ INCORRECT - Invalid formats
var invalid1 = date.SetTime("9 AM"); // Missing minutes
var invalid2 = date.SetTime("25:00"); // Invalid hour
var invalid3 = date.SetTime("9:00 PM EST"); // Timezone not supported
  1. Validate Time String Before Use:
public DateTime SafeSetTime(DateTime date, string timeString)
{
    try
    {
        return date.SetTime(timeString);
    }
    catch (FormatException)
    {
        // Log error and return safe default
        SystemInfo.Error($"Invalid time format: {timeString}");
        return date.SetTime("12:00 AM"); // Default to start of day
    }
}

Problem: Performance Issues with Large Datasets

Symptoms:

  • Slow performance when converting many dates
  • Timeout errors when processing large lists

Solutions:

  1. Batch Process Efficiently:
// ✅ EFFICIENT - Convert in memory after query
var records = Database.Query<Activity>()
    .Where(a => a.OwnerId == userId)
    .ToList(); // Execute query first

// Then convert times in memory
var displayRecords = records.Select(r => new
{
    Subject = r.Subject,
    LocalTime = r.CreatedDate.ToUserTime()
}).ToList();

// ❌ INEFFICIENT - Multiple database round trips
foreach (var record in records)
{
    var detail = Database.Retrieve<Activity>(record.Id); // Extra query!
    var localTime = detail.CreatedDate.ToUserTime();
}
  1. Cache Timezone Information:
// ✅ EFFICIENT - Cache offset
TimeSpan userOffset = DateTimeUtil.GetUserTimezoneOffset();
var times = records.Select(r => r.CreatedDate.Add(userOffset)).ToList();

// ✅ BETTER - Use ToUserTime() which is optimized
var times = records.Select(r => r.CreatedDate.ToUserTime()).ToList();

Problem: Daylight Saving Time Issues

Symptoms:

  • Times off by exactly 1 hour during DST transitions
  • Inconsistent results across different dates

Solutions:

  1. Use Timezone Methods (Not Manual Offsets):
// ✅ CORRECT - Handles DST automatically
DateTime userTime = utcTime.ToUserTime();

// ❌ INCORRECT - Fixed offset doesn't account for DST
TimeSpan fixedOffset = TimeSpan.FromHours(-5); // EST
DateTime wrongTime = utcTime.Add(fixedOffset); // Wrong during DST!
  1. Verify Timezone Offset for Specific Dates:
// Check offset for specific date (accounts for DST)
DateTime summerDate = new DateTime(2025, 7, 15, 0, 0, 0, DateTimeKind.Utc);
DateTime winterDate = new DateTime(2025, 1, 15, 0, 0, 0, DateTimeKind.Utc);

var summerTime = summerDate.ToUserTime();
var winterTime = winterDate.ToUserTime();

// Offsets will differ if timezone observes DST

Additional Notes

Localization Support

The ToRelativeTime() methods automatically use WebResources for localized strings based on the user's culture settings. This ensures relative time displays appear in the user's language:

  • English: "5 minutes ago", "2 hours ago"
  • French: "il y a 5 minutes", "il y a 2 heures"
  • Spanish: "hace 5 minutos", "hace 2 horas"

Related Documentation

  • SystemInfo Class - Access user and company information, including timezone preferences
  • FieldDateTime Component - UI component with automatic timezone conversion
  • InputField Component - Form input component with automatic date/time handling