{"id":94965,"date":"2016-12-19T07:00:00","date_gmt":"2016-12-19T22:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/?p=94965"},"modified":"2019-03-13T10:34:58","modified_gmt":"2019-03-13T17:34:58","slug":"20161219-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20161219-00\/?p=94965","title":{"rendered":"Why can&#8217;t VarDateFromStr parse back a Hungarian date that was generated by VarBstrFromDate?"},"content":{"rendered":"<p>A customer liaison reported a problem with date parsing. <\/p>\n<p>Ugh, date parsing. <\/p>\n<blockquote CLASS=\"q\">\n<p>The customer is receiving date information from a scanner that they want to parse. They are using the <code>COle&shy;Date&shy;Time::Parse&shy;Date&shy;Time<\/code> method. The customer reports that clients in Hungary (locale 1038) are unable to parse dates. The call to <code>COle&shy;Date&shy;Time::Parse&shy;Date&shy;Time<\/code> fails with <code>false<\/code>. That method internally uses <code>Var&shy;Date&shy;From&shy;Str<\/code>, and calling <code>Var&shy;Date&shy;From&shy;Str<\/code> directly fails with the error <code>DISP_E_TYPE&shy;MISMATCH<\/code>. <\/p>\n<p>This problem is observed only for Hungarian. <\/p>\n<\/blockquote>\n<p>The customer included a demonstration program that calls methods on <code>COle&shy;Date&shy;Time<\/code>, but I&#8217;ve stripped away the wrapper below, so we can focus on the problem better. <\/p>\n<pre>\nLCID hungarian = MAKELANGID(LANG_HUNGARIAN, SUBLANG_HUNGARIAN_HUNGARY);\nDATE date = ...; \/\/ something\nBSTR str;\nhr = VarBstrFromDate(date, hungarian, VAR_DATEVALUEONLY, &amp;str);\n\/\/ The call to VarBstrFromDate succeeds and returns something like\n\/\/ \"2010. 12. 05\". Now let's try to parse it back.\n\nhr = VarDateFromStr(str, hungarian, VAR_DATEVALUEONLY, &amp;date);\n\/\/ The attempt to parse back to a date fails with DISP_E_TYPEMISMATCH.\n<\/pre>\n<p>The customer noted that this change in behavior was relatively recent. <\/p>\n<p>The reason is that the localization team in Windows 10 made a change to the date formats for Hungarian. In earlier versions of Windows, the call to <code>Var&shy;Bstr&shy;From&shy;Date<\/code> produced <code>\"2010.12.05\"<\/code>. Notice the difference? <\/p>\n<p>The date separator changed from a period to a period <i>followed by a space<\/i>. <\/p>\n<p>This highlights that <a HREF=\"https:\/\/blogs.msdn.microsoft.com\/shawnste\/2005\/04\/05\/culture-data-shouldnt-be-considered-stable-except-for-invariant\/\">culture data is not stable<\/a>. Any code that generates Hungarian-formatted dates will produce different results on Windows 10 compared to earlier versions of Windows. <\/p>\n<p>Of course, one should also note that the date formatting preferences can also be customized by the user at any time, so the statement is even stronger: Any code that generates locale-sensitive formatted dates may produce different results at any time, even within a single run of the program. <\/p>\n<p>So if your goal is to format the date as a string, with the intention of parsing it back, then you don&#8217;t want to use anything that is locale-sensitive. Instead, use a locale-insensitive format, such as ISO 8601. <\/p>\n<p>The customer said that they were getting the information from a scanner, but it wasn&#8217;t clear where the scanner was getting it from. <\/p>\n<p>If this is a timestamp generated by the scanner itself, then they should try to configure the scanner to generate timestamps in a locale-insensitive format. <\/p>\n<p>If the timestamp is coming from the document being scanned, then you need to work out who is generating the document. If the document was generated by the same program that is trying to parse the result back (which the sample code seems to be suggesting), then you should fix the program that generates the document so it uses a locale-insensitive format. For human readability, you could have it generate a locale-sensitive version of the date next to the locale-invariant version. On the other hand, if the document was generated by an external source, then you may want to implement a custom parser that handles the date format that the external source uses. <\/p>\n<p>And if you don&#8217;t know what date format the external source is using, then you&#8217;re kind of stuck. After all, a date of the form <code>12-05-2010<\/code> is ambiguous. It might be generated by somebody whose locale settings specify a date format of <code>MM-DD-YYYY<\/code>, or somebody whose locale settings specify a date format of <code>DD-MM-YYYY<\/code>. <\/p>\n<p>Okay, so we&#8217;ve addressed the customer&#8217;s problem of not being able to round-trip a date-to-string-to-date conversion. But why specifically does changing the date separator from &#8220;period&#8221; to &#8220;period and space&#8221; cause <code>Var&shy;Date&shy;From&shy;Str<\/code> to be unable to parse back a string that it generated itself? <\/p>\n<p>The string <code>2010. 12. 05.<\/code> parses back like this: <\/p>\n<ul>\n<li><code>\"2010\"<\/code> is a year, no problem there.<\/li>\n<li><code>\". \"<\/code> is a period followed by a space, no problem there.<\/li>\n<li><code>\"12\"<\/code> is a month, no problem there.<\/li>\n<li><code>\". \"<\/code> is a period followed by a space, no problem there.<\/li>\n<li><code>\"05\"<\/code> is a day, no problem there.<\/li>\n<li><code>\".\"<\/code> is a period <i>not<\/i> followed by a space,     which does not match the date separator, so this parse is rejected.<\/li>\n<li>Next, a special-case rule for <code>\".\"<\/code> kicks in     and says, &#8220;Okay, well, if normal parsing rules failed,     but I see a period after a complete date,     then treat it as a time separator.&#8221;\n<li>\n<li>And then parsing fails, because a time separator is not allowed     due to the     <code>VAR_DATE&shy;VALUE&shy;ONLY<\/code> flag.<\/li>\n<\/ul>\n<p>There is also some special-case code for Hungarian trailing period, but that code path is no longer being hit, probably because of the change from a one-character date separator to a two-character date separator. <\/p>\n<p>It turns out that the date parsing code has a ton of special-case rules for various languages. (I&#8217;m looking at you, Polish, with your genitive month forms.) <\/p>\n<p>Now it looks like it needs a ton plus one. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Parsing is hard. Date parsing doubly so.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-94965","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Parsing is hard. Date parsing doubly so.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/94965","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=94965"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/94965\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=94965"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=94965"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=94965"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}