c# making a short date ends up adding one day - c#

I am trying to make a short date, but in the result I get one day more. With date like "2014-01-03 00:00:00" its okay, but it fails when time is "23:59:59".
EntryDate= "2014-01-03 23:59:59"
but getting result = "2014-01-04"
try
{
DateTime exact = DateTime.ParseExact(EntryDate, "yyyyMMdd", (IFormatProvider)CultureInfo.InvariantCulture);
mventryAttrib.Value = (exact.ToLocalTime().ToString("yyyy-MM-dd"));
}
catch (FormatException ex)
{
try
{
DateTime exact = DateTime.ParseExact(EntryDate, "yyyy-MM-dd HH:mm:ss", (IFormatProvider)CultureInfo.InvariantCulture);
mventryAttrib.Value = (exact.ToLocalTime().ToString("yyyy-MM-dd"));
}
catch
{
}

This is due to ParseExact returns a DateTime with a Kind property value of DateTimeKind.Unspecified.
This, when coupled with a call to .ToLocalTime() when you're in a timezone that has a positive offset from UTC, will bump the DateTime value forward by that many hours and return a DateTime value with a Kind property value of DateTimeKind.Local.
Here is a short program that will demonstrate:
var exact = DateTime.ParseExact("2014-01-03 23:59:59", "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
Console.WriteLine($"{exact} - {exact.Kind}");
var local = exact.ToLocalTime();
Console.WriteLine($"{local} - {local.Kind}");
Console.WriteLine(TimeZone.CurrentTimeZone.GetUtcOffset(exact));
Output (on my machine):
03.01.2014 23.59.59 - Unspecified
04.01.2014 00.59.59 - Local
01:00.00
If you intended the parsed DateTime value to be local from the outset you should make a new value that is specifically local, with the same values:
exact = new DateTime(exact.Ticks, DateTimeKind.Local);
Be aware though that this may have unforseen consequences when dealing with timezone boundaries. I would urge you to find a better library than the built in DateTime types, such as Noda Time.

It looks as though you are setting the time in exact as a UTC time and then converting this to a local time. This conversion is adding a number of hours to the time and consequently moving the date along.
Try exact.ToUniversalTime() and you should get the date you set.

I think this site could help you to solve the problem.
https://msdn.microsoft.com/en-gb/library/8kb3ddd4(v=vs.110).aspx

Related

Nodatime create a ZonedDateTime given a time and timezone

Can anyone give me the most straightforward way to create a ZonedDateTime, given "4:30pm" and "America/Chicago".
I want this object to represent that time for the current date in that timezone.
Thanks!
I tried this... but it seems to actually give me an instant in the local timezone which gets offset when creating the zonedDateTime.
string time = "4:30pm";
string timezone = "America/Chicago";
DateTime dateTime;
if (DateTime.TryParse(time, out dateTime))
{
var instant = new Instant(dateTime.Ticks);
DateTimeZone tz = DateTimeZoneProviders.Tzdb[timezone];
var zonedDateTime = instant.InZone(tz);
using NodaTime;
using NodaTime.Text;
// your inputs
string time = "4:30pm";
string timezone = "America/Chicago";
// parse the time string using Noda Time's pattern API
LocalTimePattern pattern = LocalTimePattern.CreateWithCurrentCulture("h:mmtt");
ParseResult<LocalTime> parseResult = pattern.Parse(time);
if (!parseResult.Success) {
// handle parse failure
}
LocalTime localTime = parseResult.Value;
// get the current date in the target time zone
DateTimeZone tz = DateTimeZoneProviders.Tzdb[timezone];
IClock clock = SystemClock.Instance;
Instant now = clock.Now;
LocalDate today = now.InZone(tz).Date;
// combine the date and time
LocalDateTime ldt = today.At(localTime);
// bind it to the time zone
ZonedDateTime result = ldt.InZoneLeniently(tz);
A few notes:
I intentionally separated many items into separate variables so you could see the progression from one type to the next. You may condense them as desired for fewer lines of code. I also used the explicit type names. Feel free to use var.
You may want to put this in a function. When you do, you should pass in the clock variable as a parameter. This will let you replace the system clock for a FakeClock in your unit tests.
Be sure to understand how InZoneLeniently behaves, and note how it's changing in the upcoming 2.0 release. See "Lenient resolver changes" in the 2.x migration guide.

DateTime.Parse - changes format

I'll try to illustrate an example:
var dateNow = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss");
produces: 10/01/2014 21:50:34
var dateNowParse = DateTime.Parse(DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"));
produces: 10/01/2014 9:50:34 PM
QUESTION:
How to parse the date, and keep formatting like: dd/MM/yyyy HH:mm:ss, with an 24 hour format, without any PM
Thank you!
Update 1
Sorry maybe my question wasn't so clear, i'll try to explain the real situation below.
Please do not focus on real meaning of DateTime.Now, suppose we have a string variable in the format of 10/01/2014 21:50:34, and then I try to parse it, and store the result in another variable. What I am willing to achieve is to keep the result in a DateTime variable which has the exact formatting 10/01/2014 21:50:34.
Now here is a snippet:
var stringDate = "10/01/2014 22:50:30";
DateTime parsedDate = DateTime.Parse(stringDate, CultureInfo.InvariantCulture);
//parsedDate result is: 10/01/2014 10:50:30 PM
What is frustrating me is:
In the stringDate the 22:50 hour says that the string is formatted to the 24 hour clock. (the 12 clock format uses hours counter up to 12)
If I used 22:50, Isn't logically that the output should'nt use any AM PM and 12 hour format?
How to parse the date, and keep formatting
You need to keep the format alongside the DateTime if you want to. A DateTime does not have any concept of being in a particular format. The value of the DateTime returned by Parse isn't "10/01/2014 9:50:34 PM" - it's that particular date and time, but not a string representation.
You could have a type which maintains the two together - or if you always want to format in the same way, just specify that format explicitly when you format, without keeping it as data with the DateTime value.
Personally I would try to stick to DateTime.ParseExact where feasible, as I find it easier to predict what it will do - but it does depend on your input. If it's input with a particular format that you're expecting, ParseExact really is the way forward, potentially with the invariant culture to avoid any cultural differences.
I would store the date now as a date
DateTime dateNow = DateTime.Now;
then when you need to display it with that formatting
String strNow = dateNow.ToString("dd/MM/yyyy HH:mm:ss");
If you have a date coming in with a format say in a String variable strNow and want to put it in the DateTime I would make sure to catch format exceptions
DateTime dateNow;
try {
dateNow = DateTime.ParseExact(strNow, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture);
}
catch (FormatException) {
//Log something or set a default date.
}
DateTime.ParseExact(DateTime, Format, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite);
for example:
DateTime.ParseExact(strNow, "dd/MM/yyyy HH:mm:ss", DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite);

Calculating start of day and start of month in UTC

I have users that can be in different timezones and I'm looking to determine the UTC value of the beginning of their days and their months. Inside an object, I have a method that attempts to do that; it looks like this:
private void SetUserStartTimesUTC()
{
DateTime TheNow = DateTime.UtcNow.ConvertUTCTimeToUserTime(this.UserTimezoneID);
DateTime TheUserStartDateUserTime = TheNow.Date;
DateTime TheUserStartMonthUserTime = new DateTime(TheNow.Year, TheNow.Month, 1);
DateTime TheUserEndMonthUserTime = TheUserStartMonthUserTime.AddMonths(1);
this.UserStartOfDayUTC = TheUserStartDateUserTime.ConvertUserTimeToUTCTime(this.UserTimezoneID);
this.UserStartOfMonthUTC = TheUserStartMonthUserTime.ConvertUserTimeToUTCTime(this.UserTimezoneID);
this.UserEndOfMonthUTC = TheUserEndMonthUserTime.ConvertUserTimeToUTCTime(this.UserTimezoneID);
}
And this method depends on two other extension methods that do the conversions between a user's time and UTC time
public static DateTime ConvertUserTimeToUTCTime(this DateTime TheUserTime, string TheTimezoneID)
{
TimeZoneInfo TheTZ = TimeZoneInfo.FindSystemTimeZoneById(TheTimezoneID);
DateTime TheUTCTime = new DateTime();
if (TheTZ != null)
{
DateTime UserTime = new DateTime(TheUserTime.Year, TheUserTime.Month, TheUserTime.Day, TheUserTime.Hour, TheUserTime.Minute, TheUserTime.Second);
TheUTCTime = TimeZoneInfo.ConvertTimeToUtc(UserTime, TheTZ);
}
return TheUTCTime;
}
public static DateTime ConvertUTCTimeToUserTime(this DateTime TheUTCTime, string TheTimezoneID)
{
TimeZoneInfo TheTZ = TimeZoneInfo.FindSystemTimeZoneById(TheTimezoneID);
DateTime TheUserTime = new DateTime();
if (TheTZ != null)
{
DateTime UTCTime = new DateTime(TheUTCTime.Year, TheUTCTime.Month, TheUTCTime.Day, TheUTCTime.Hour, TheUTCTime.Minute, 0, DateTimeKind.Utc);
TheUserTime = TimeZoneInfo.ConvertTime(UTCTime, TheTZ);
}
return TheUserTime;
}
Now I've been dealing with timezone issues for a while and I know that timezone issues can introduce off-by-one bugs that can be hard to detect.
Does my implementation of timezones seem to be correct or is there an edge case that will create some sort of off-by-one bug?
Thanks for your suggestions.
Your methods seem needlessly complicated, to be honest.
Why would you have a parameter called TheUTCTime and then create a UTC version of it? Shouldn't it already have a Kind of UTC? Even if it didn't, you would be better off using DateTime.SpecifyKind - currently when converting one way you wipe out the seconds, whereas converting the other way you don't... in both cases you wipe out any sub-second values.
Also:
TimeZoneInfo.FindSystemTimeZoneById never returns null
Returning new DateTime() (i.e. January 1st 0001 AD) if the time zone can't be found seems like a really poor way of indicating an error
There's no need to have a local variable in your conversion methods; just return the result of calling ConvertTime directly
Your "end of month" is really "start of the next month"; that may be what you want, but it's not clear.
Personally I would strongly advise you to avoid the BCL DateTime for all of this entirely. I'm entirely biased being the main author, but I'd at least hope that you'd find Noda Time more pleasant to work with... it separates out the idea of "date with no time component", "time with no date component", "local date and time with no specific time zone" and "date and time in a particular time zone"... so the type system helps you to only do sensible things.
EDIT: If you really have to do this within the BCL types, I'd write it like this:
private void SetUserStartTimesUTC()
{
DateTime nowUtc = DateTime.UtcNow;
TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById(UserTimeZoneID);
// User-local values, all with a Kind of Unspecified.
DateTime now = TimeZoneInfo.ConvertTime(nowUtc, zone);
DateTime today = now.Date;
DateTime startOfThisMonth = todayUser.AddDays(1 - today.Day);
DateTime startOfNextMonth = startOfThisMonth.AddMonths(1);
// Now convert back to UTC... see note below
UserStartOfDayUTC = TimeZoneInfo.ConvertTimeToUtc(today, zone);
UserStartOfMonthUTC = TimeZoneInfo.ConvertTimeToUtc(startOfThisMonth, zone);
UserEndOfMonthUTC = TimeZoneInfo.ConvertTimeToUtc(startOfNextMonth, zone);
}
The extension methods you've added really don't provide much benefit, as you can see.
Now, the code mentions a "note" - you're currently always assuming that midnight always exists and is unambiguous. That's not true in all time zones. For example, in Brazil, on daylight saving changes forward, the time skips from midnight to 1am - so midnight itself is invalid, basically.
In Noda Time we fix this by having DateTimeZone.AtStartOfDay(LocalDate) but it's not as easy with the BCL.
For comparison, the equivalent Noda Time code would look like this:
private void SetUserStartTimesUTC()
{
// clock would be a dependency; you *could* use SystemClock.Instance.Now,
// but the code would be much more testable if you injected it.
Instant now = clock.Now;
// You can choose to use TZDB or the BCL time zones
DateTimeZone zone = zoneProvider.FindSystemTimeZoneById(UserTimeZoneID);
LocalDateTime userLocalNow = now.InZone(zone);
LocalDate today = userLocalNow.Date;
LocalDate startOfThisMonth = today.PlusDays(1 - today.Day);
LocalDate startOfNextMonth = startOfThisMonth.PlusMonths(1);
UserStartOfDayUTC = zone.AtStartOfDay(today);
UserStartOfMonthUTC = zone.AtStartOfDay(startOfThisMonth);
UserEndOfMonthUTC = zone.AtStartOfDay(startOfNextMonth);
}
... where the properties would be of type ZonedDateTime (which remembers the time zone). You could change them to be of type Instant (which is just a point in time) if you want, just chaining a ToInstant call for each property setter.

Convert datetime entered by user to UTC

The user enters a date and a time in separate textboxes. I then combine the date and time into a datetime. I need to convert this datetime to UTC to save it in the database. I have the user's time zone id saved in the database (they select it when they register). First, I tried the following:
string userTimeZoneID = "sometimezone"; // Retrieved from database
TimeZoneInfo userTimeZone = TimeZoneInfo.FindSystemTimeZoneById(userTimeZoneID);
DateTime dateOnly = someDate;
DateTime timeOnly = someTime;
DateTime combinedDateTime = dateOnly.Add(timeOnly.TimeOfDay);
DateTime convertedTime = TimeZoneInfo.ConvertTimeToUtc(combinedDateTime, userTimeZone);
This resulted in an exception:
The conversion could not be completed because the supplied DateTime did not have the Kind property set correctly. For example, when the Kind property is DateTimeKind.Local, the source time zone must be TimeZoneInfo.Local
I then tried setting the Kind property like so:
DateTime.SpecifyKind(combinedDateTime, DateTimeKind.Local);
This didn't work, so I tried:
DateTime.SpecifyKind(combinedDateTime, DateTimeKind.Unspecified);
This didn't work either. Can anyone explain what I need to do? Am I even going about this the correct way? Should I be using DateTimeOffset?
Just like all the other methods on DateTime, SpecifyKind doesn't change an existing value - it returns a new value. You need:
combinedDateTime = DateTime.SpecifyKind(combinedDateTime,
DateTimeKind.Unspecified);
Personally I'd recommend using Noda Time which makes this kind of thing rather clearer in my rather biased view (I'm the main author). You'd end up with this code instead:
DateTimeZone zone = ...;
LocalDate date = ...;
LocalTime time = ...;
LocalDateTime combined = date + time;
ZonedDateTime zoned = combined.InZoneLeniently(zone);
// You can now get the "Instant", or convert to UTC, or whatever...
The "leniently" part is because when you convert local times to a specific zone, there's the possibility for the local value being invalid or ambiguous in the time zone due to DST changes.
You can also try this
var combinedLocalTime = new DateTime((dateOnly + timeOnly.TimeOfDay).Ticks,DateTimeKind.Local);
var utcTime = combinedLocalTime.ToUniversalTime();

How to validate if a "date and time" string only has a time?

I have a single string variable that stores what could be a full date or a partial date:
1) Full Date: 12/12/2010 12:33 AM
2) Partial Date: 12:33 AM (no date field only time)
I'm trying to figure out what would be the best approach to parse the string to figure out if the string is missing the date string or not. The reason is, in my code if the date is missing I will append a default date to the string (such as 1/1/1900). Keep in mind that the time could be in various formats.
Update - My particular answer to this problem.
As all the "posts" have stated, there are multiple answers to this problem, this is ultimately what I used and hope it can help others*:
public DateTime ProcessDateAndTime(string dateString)
{
string dateAndTimeString = dateString;
string[] timeFormats = new string[]
{
"hh:mm tt", "hh:mm:ss tt",
"h:mm tt", "h:mm:ss tt",
"HH:mm:ss", "HH:mm", "H:mm"
};
// check to see if the date string has a time only
DateTime dateTimeTemp;
if (DateTime.TryParseExact(dateString, timeFormats,
CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.None, out dateTimeTemp))
{
// setting date to 01/01/1900
dateAndTimeString = new DateTime(1900, 1, 1).ToShortDateString() + " " + dateString;
}
return DateTime.Parse(dateAndTimeString);
}
*Note: This method is based on the assumption that there are only a specific amount of time formats used in your application, and that it is guaranteed that a properly formatted date and time, or time only string passed in (pre-validation for removal of garbage text).
Use
Convert.ToDateTime(string value, IFormatProviderProvider provider)
Since the string comes in different flavors, provide different format providers as needed.
The order could be:
DateOnly
Time Only
DateTime
The convert will throw an format exception if it fails. If you prefer not to have exceptions, use Datetime.TryParse instead as that returns a boolean.
Depending on how the string is represented you could have more than 3 format providers.
You can try to validate string with a RegEx,
BTW, good regexes for DateTime validation can be found here
Here's one way to do this without knowing all possible time formats:
CultureInfo provider = CultureInfo.CurrentCulture;
DateTime time;
DateTime datetime;
bool isTime = DateTime.TryParse(dateString, provider, DateTimeStyles.NoCurrentDateDefault, out time)
&& time.Date == DateTime.MinValue.Date
&& DateTime.TryParse(dateString, provider, DateTimeStyles.None, out datetime)
&& datetime.Date != DateTime.MinValue.Date);
If the string only has a time then the first TryParse will set the date part to 1/1/0001 or DateTime.MinValue.Date and the second TryParse will set the date part to the current date. This will work unless it is run by Doctor Who after travelling back in time to 1/1/0001.
You can use DateTime.TryParseExact.
This might not be the best but it answers your question:
string dateString = "9:53AM";
if (!dateString.Contains('/')))
{
dateString = DateTime.Now.ToShortDateString() + " " + dateString;
}
Looking at the length of the string will be straight-forward and will support multiple formats. A string with a date and time will most certainly be longer than a string with just a time. However, if your input may have times with high precision (12:30:30:50:20 vs 12/11/11 12:30) and low precision this won't work.
This solution is ideal if you don't need to know the value in the string immediately, and only want to add the default date.
If you support times to the second, for instance, a time will have 8 or less characters and a date-time will have 9 or more.
Given that the time can be in various formats (12/24?) it would be best to user several patterns, in some pre-defined order, trying to parse with each and resolving when the first succeeds.
You can also try
DateTime aTime;
if (DateTime.TryParse(dateString, CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.NoCurrentDateDefault, out aTime))
{
//if the there is no date part in the dateString, date will
// default to Gregorian 1/1/0001
}

Categories

Resources