To display datetimes in the user's local timezone while preserving backend UTC storage, you can use JavaScript's built-in timezone offset support:
const utcDate = "2025-05-27T20:03:00Z";
const localDisplay = new Date(utcDate).toLocaleString();
// → "5/27/2025, 10:03 PM" (depending on user's locale)
This gives users a familiar and correctly adjusted view of time. For a consistent format, Intl.DateTimeFormat
can be used.
ASP.NET Core with System.Text.Json
handles ISO 8601 UTC strings automatically when binding to DateTime
properties. Ensure you're not converting to UTC again if the incoming data already ends with Z
.
Best Practices:
[JsonPropertyName("created")]
public DateTime Created { get; set; } // Will be parsed as UTC if ends in "Z"
If needed, ensure correct serialization:
private readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new UtcDateTimeConverter() }
};
Custom converter (if required):
public class UtcDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> DateTime.SpecifyKind(reader.GetDateTime(), DateTimeKind.Utc);
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
}
When a user selects a datetime in a <input type="datetime-local">
, the returned value (e.g., "2025-05-27T22:03"
) is in local time. To maintain UTC consistency on the backend, this must be converted to UTC.
Implementation:
const handleChange = (e) => {
const local = new Date(e.target.value); // local time
const utc = local.toISOString(); // UTC string for API
setPost({ ...post, created: utc });
};
This ensures accurate time data regardless of the user's timezone.
To populate an <input type="datetime-local">
in React, you must convert your UTC string into a local time string formatted as "yyyy-MM-ddTHH:mm"
— the only format the input accepts.
Implementation:
function toDatetimeLocalValue(dateInput) {
const date = new Date(dateInput);
const pad = (n) => n.toString().padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
}
Use this function when binding input values in forms to ensure users see time in their own timezone.
When working with frontend frameworks like React and backend APIs such as ASP.NET Core, ensuring consistent and accurate DateTime handling across timezones is essential — especially when dealing with user input via <input type="datetime-local">
and when storing timestamps like created
, modified
, and published
in UTC in a database.
This Snippset explores a practical and professional strategy for:
Accepting and formatting user input in local time
Converting local times to UTC before sending to the server
Storing and retrieving UTC in ASP.NET Core
Formatting and displaying local time correctly on the frontend
All examples assume the use of modern JSON APIs with ISO 8601 strings ("2025-05-27T20:03:00Z"
) and an object-based form model in React.
Overview: In many situations, you may need to store more than just simple values in an array. Extending arrays to include attributes such as gender, language, or other metadata can add significant flexibility to your data. This Snipp shows how to structure an array to include such attributes.
Implementation:
const namesWithAttributes = [
{ name: "Norbert", gender: "male", language: "German" },
{ name: "Manuela", gender: "female", language: "Spanish" },
{ name: "Saskia", gender: "female", language: "Dutch" },
{ name: "Henrik", gender: "male", language: "Swedish" },
{ name: "Claus", gender: "male", language: "Danish" },
];
In this example, each item in the array is an object containing a name
, gender
, and language
. This structure allows you to filter and manipulate the data based on multiple attributes easily.
Overview: In some cases, you may want to return only specific fields from filtered data, such as extracting only the name
from a list of people. This Snipp demonstrates how to combine filtering and mapping to achieve this.
Implementation:
// Filter by gender and return only names
const maleNames = namesWithAttributes
.filter(person => person.gender === "male")
.map(person => person.name);
console.log(maleNames);
// Output: ["Norbert", "Henrik", "Claus"]
This approach combines the filter()
and map()
methods to first filter the data by gender and then extract only the name
field from the resulting objects.
Overview: Sorting data alphabetically can help organize information in a more accessible way. This Snipp demonstrates how to sort an array of objects alphabetically based on a specific field, such as name
.
Implementation:
// Sort the names alphabetically
const sortedNames = namesWithAttributes.slice().sort((a, b) => a.name.localeCompare(b.name));
console.log(sortedNames);
// Output: [{ name: "Claus", gender: "male", language: "Danish" }, { name: "Henrik", gender: "male", language: "Swedish" }, { name: "Manuela", gender: "female", language: "Spanish" }, { name: "Norbert", gender: "male", language: "German" }, { name: "Saskia", gender: "female", language: "Dutch" }]
In this example, the slice()
method is used to create a copy of the array, and sort()
is used to sort the name
field alphabetically.
Overview: After filtering data, you may only need specific fields, such as the name
attribute. This Snipp explains how to use the map()
method to extract specific fields from filtered data.
Implementation:
// Filter and extract only the names
const germanNames = namesWithAttributes
.filter(person => person.language === "German")
.map(person => person.name);
console.log(germanNames);
// Output: ["Norbert"]
Here, after filtering by language
, we use map()
to return just the name
values from the filtered array. This results in an array of names that meet the filtering criteria.
Overview: Sometimes, filtering based on multiple attributes is necessary. This Snipp demonstrates how to combine different conditions in the filter()
method to retrieve data that satisfies more than one criterion.
Implementation:
// Filter by gender and language
const femaleSpanishNames = namesWithAttributes.filter(person => person.gender === "female" && person.language === "Spanish");
console.log(femaleSpanishNames);
// Output: [{ name: "Manuela", gender: "female", language: "Spanish" }]
In this example, the array is filtered to include only objects where the gender
is female
and the language
is Spanish
. Using multiple conditions ensures that only the most relevant data is retrieved.