1

Given a Local DateTime value on a computer configured for PST (which will implicitly change to PDT on March 10th when DST kicks in), how can one obtain a string including the appropriate timezone - eg. PST/PDT, not offset! - in the output?

DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ???")

Expected output strings, eg:

"2019-02-09 13:04:22 PST"  // right after lunch today
"2019-04-09 13:04:22 PDT"  // right after lunch in two months

The MSDN DateTime Custom Format Strings page shows examples of explicitly hard-coding "PST" into the output, which will be wrong half the year and/or when the local TZ is changed. Computers and people move, so hard-coding TZ values is simply 'not appropriate'.

Preferably this can be done with just a Format String, allowing DateTime values to be supplied until the rendering/to-string phase - although there does not appear to be a 'ZZZ' format. I've specified 'Local' DateTime Kind to, hopefully, reduce some additional quirks..

user2864740
  • 60,010
  • 15
  • 145
  • 220
  • Did you try the `TimeZoneInfo`? From there you can create an extension method which you can use on any DateTime property. https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.displayname?view=netframework-4.7.2 – bene_rawr Feb 09 '19 at 21:35
  • @bene_rawr I'm primarily dealing with some old, icky ASP `<%= Eval("Property", "FormatString") %>` code and am hoping, perhaps futilely, that there is some string format magic I've missed :} – user2864740 Feb 09 '19 at 21:36
  • @bene_rawr Although I suppose that's a fair option here (it wouldn't be in cases where only a format string is allowed): `<%# ToAwesomeFormat(Eval(...)) %>` – user2864740 Feb 09 '19 at 21:44
  • 1
    All `FormatString`s are listed in the link you just posted. So there isn't really more magic about that. – bene_rawr Feb 09 '19 at 21:44
  • 1
    What makes you think that a time zone abbreviation actually exists for every time zone and every language in the world? Even, then what makes you think that a time zone abbreviation is enough to identify the time zone? Hint - both questions are amorphous. Consider "CST" or "IST" - Each have three or four places in the world that they might belong to. Many many many other cases as well... – Matt Johnson-Pint Feb 10 '19 at 00:24
  • 1
    Consider using DateTimeOffset and simply displaying the offset instead of some non-standard time zone abbreviation – Dave M Feb 10 '19 at 04:50

2 Answers2

2

Since a DateTime instance does not keep timezone information, there is no way to do it with custom date and time format strings. "zzz" specifier is for UTC Offset value, DateTime.Kind with "K" specifier does not reflect time zone abbrevation neither. Both are useless for your case.

However, there is nuget package called TimeZoneNames which is written by time zone geek Matt Johnson that you can get abbreviations of a timezone name (supports both IANA and Windows time zone identifier)

var tz = TZNames.GetAbbreviationsForTimeZone("Pacific Standard Time", "en-US");
Console.WriteLine(tz.Standard); // PST
Console.WriteLine(tz.Daylight); // PDT

If you wanna get your windows time zone identifier programmatically, you can use TimeZoneInfo.Local.Id property, if you wanna get the current language code, you can use CultureInfo.CurrentCulture.Name property by the way.

var tz = TZNames.GetAbbreviationsForTimeZone(TimeZoneInfo.Local.Id, CultureInfo.CurrentCulture.Name);

But before that, you should check your local time is daylight saving time to choose which abbreviation to append your formatted string.

DateTime now = DateTime.Now;
bool isDaylight = TimeZoneInfo.Local.IsDaylightSavingTime(now);

If isDaylight is true, you should use the result of the TimeZoneValues.Daylight property, otherwise you should use TimeZoneValues.Standard property of the first code part.

At the end, you need append one of those abbreviation at the end of DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss) string.

For an important note from Matt on the package page;

Time zone abbreviations are sometimes inconsistent, and are not necessarily localized correctly for every time zone. In most cases, you should use abbreviations for end-user display output only. Do not attempt to use abbreviations when parsing input.

Second important note from Matt's comment;

What makes you think that a time zone abbreviation actually exists for every time zone and every language in the world? Even, then what makes you think that a time zone abbreviation is enough to identify the time zone? Hint - both questions are amorphous. Consider "CST" or "IST" - Each have three or four places in the world that they might belong to. Many many many other cases as well...

Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
  • 2
    Even I am regretting putting abbreviations into my TimeZoneNames library. Full names like "Eastern Standard Time" - sure, I can rely on CLDR for that. But there is just no great source of time zone abbreviations anywhere that is consistent and widely accepted. TZDB and CLDR are both failures in this regard. Someone would have to spend a lot more time than I have to curate these into various "sets" of acceptable abbreviations, and figure out under what criteria they are acceptable. IMHO - if you can avoid time zone abbreviations altogether, you should. – Matt Johnson-Pint Feb 10 '19 at 00:27
  • 1
    @MattJohnson I feel your pain my friend. Even someone (I wish I had) spend lots of time on this subject, since the sources are not reliable - and probably never will -, the process would be pain in the ass. As you [mentioned](https://stackoverflow.com/questions/54610867/format-local-datetime-with-appropriate-local-dst-aware-timezone-eg-pst-or-pdt#comment96018589_54610867), they are _not_ unique also. Even when you show "CST" as an output, this will be always confusing for who reads it. – Soner Gönül Feb 10 '19 at 08:20
1

TimeZoneInfo should be able to help here: https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.standardname?view=netframework-4.7.2

It looks like TimeZoneInfo gives full names ("Pacific Standard Time"/"Pacific Daylight Time") rather than abbreviations ("PST"/"PDT"). It that's a problem, you'll still need to find a source for the short names. There are some ideas on how to do that here: Timezone Abbreviations

using System;
using Xunit;

namespace Q54610867
{
    public class TimeZoneTests
    {
        // I'm on Mac/Unix. If you're on Windows, change the ID to "Pacific Standard Time"
        // See: https://github.com/dotnet/corefx/issues/2538
        readonly TimeZoneInfo pacificStandardTime = TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles");

        [Fact]
        public void Today()
        {
            var today = new DateTime(2019, 2, 9, 13, 4, 22, DateTimeKind.Local);

            Assert.Equal("2019-02-09 13:04:22 Pacific Standard Time", ToStringWithTz(today, pacificStandardTime));
        }

        [Fact]
        public void future()
        {
            var future = new DateTime(2019, 4, 9, 13, 4, 22, DateTimeKind.Local);

            Assert.Equal("2019-04-09 13:04:22 Pacific Daylight Time", ToStringWithTz(future, pacificStandardTime));
        }

        static string ToStringWithTz(DateTime dateTime, TimeZoneInfo tz)
            => $"{dateTime.ToString("yyyy-MM-dd HH:mm:ss")} {(tz.IsDaylightSavingTime(dateTime) ? tz.DaylightName : tz.StandardName)}";
    }
}
xander
  • 1,689
  • 10
  • 18
  • There appears to be some glue required to a suitable TZ for a "a local time at a specific (past or future) date", which isn't always the current time D: – user2864740 Feb 09 '19 at 21:58
  • I've added a working example using the example dates from your original post. Based on my understanding, it does everything you asked for (except for using long names instead of abbreviations). Can you give a specific example of what's missing? – xander Feb 09 '19 at 22:26
  • One point besides the abbreviations rants I've made elsewhere on this page ;) - The strings behind the `StandardName` and `DaylightName` properties are horribly maintained. There are lots of weird quirks that just don't work correctly in actual human usage, like "GMT Daylight Time" or "Russia TZ 11 Standard Time" (yes, actual strings returned from this API). Windows is just not so great about this particular data. Also, the language used by these strings is the OS language, not the .NET current culture - so you can't vary them by user. – Matt Johnson-Pint Feb 10 '19 at 00:33
  • 1
    CLDR does a much better job with the localized names of time zones actually used around the world, so I've used CLDR data in my TimeZoneNames library, as shown in Soner's answer. Abbreviations are still sh*t though - and not much to be done about it. – Matt Johnson-Pint Feb 10 '19 at 00:34
  • 1
    Oh, and you said you're on Mac / Unix. Are you actually getting "Pacific Standard Time" and "Pacific Daylight Time" now? If so, then they must be using CLDR data there now. Otherwise it would just show "PST" and "PDT" abbreviations from the TZDB (but would fail miserably in other parts of the world). – Matt Johnson-Pint Feb 10 '19 at 00:37
  • 1
    The code I posted runs, and the assertions pass in my dev environment (.NET Core on OSX). `TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles").DaylightName` returns `Pacific Standard Time`. – xander Feb 10 '19 at 00:48