ODP.NET: Oracle date formatting to "output-only" formats - c#

I'd like to use Oracle date formatting in C#, such as in to_char(date, format), without database connection nor writing my own format interpreter.
Sample application (I'm using Oracle Data Provider for .NET):
namespace OracleDateToCharTest
{
using System;
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;
class Program
{
/// <param name="fmt"> Oracle-like format string. </param>
public static string OracleDateToChar(DateTime date, string fmt)
{
//// preparing format for ToString()
OracleGlobalization og = OracleGlobalization.GetThreadInfo();
string prevFormat = og.DateFormat;
og.DateFormat = fmt;
try
{
//// converting to OracleDate
OracleDate odacDate = new OracleDate(date);
//// setting format for ToString()
OracleGlobalization.SetThreadInfo(og);
return odacDate.ToString();
}
catch (OracleTypeException ex)
{
if (ex.Number == 1820)
{
//// described issue
}
throw;
}
finally
{
og.DateFormat = prevFormat;
OracleGlobalization.SetThreadInfo(og);
}
}
static void Main(string[] args)
{
var x = OracleDateToChar(DateTime.Now, "mm-dd-yyyy");
var y = OracleDateToChar(DateTime.Now, "mm-dd-yyyy HH24:Mi:ss");
var z = OracleDateToChar(DateTime.Now, "IW"); //// exception
}
}
}
It works well with formats like "mm-dd-yyyy", "mm-dd-yyyy HH24:Mi:ss", but unfortunately it doesn't work with "output-only" formats like "IW" (output-only formats are formats that you can specify in TO*_DATETIME funcitons according to Table 9-4 on http://docs.oracle.com/cd/B28359_01/olap.111/b28126/dml_commands_1029.htm).
When I call for example
OracleDateToChar(DateTime.Now, "IW")
I get ORA-01820 format code cannot appear in date input format exception in ToString() line.
I'd understand if I got this error in ToDate() method, but in ToString() it seems to be a bug in ODP.NET.
Question: Is there a way to implement OracleDateToChar method that can handle output-only formats? (assuming that calling select to_char(:date, :fmt) from nvl; from oracle database is not an option)

The point about that Oracle documentation is that it applies to DATE_FORMAT which is part of the Oracle language. Therefore you can only execute it as a PL/SQL statement which is why you cannot get it to work client side: IW is not a valid format mask for ODP.Net globalization as far as I know. In fact the ODP globalization DateFormat defaults to whatever you have in NLS_DATE_FORMAT locally which is a full date format string, whereas IW is for getting the week of the year rather than for formatting a date itself. As far as I know, nothing in the .Net framework will recognise that Oracle-specific string, so in order for it to work you would need to issue a command to Oracle and get the result back, as per the example on that page:
CONSIDER mydatetime
DATE_FORMAT MON-RRRR-DD-HH24
That seems like a lot of overkill to change a date format. There are some good alternative siggestions which are client-side .Net in this SO answer
Further Reading
The Oracle docs for the Oracle Date Structure (OracleDate) ToString() method notes that
The returned value is a string representation of the OracleDate in the
format specified by the thread's OracleGlobalization.DateFormat
property
(My emphasis). The definition for this can be found here. A useful list of allowed format strings for NLS_DATE_FORMAT, and therefore by extension OracleDate.ToString, can be found here.

Related

DateTime with offset Parse in NodaTime

If I have a string like "2020-12-15T12:10:00.202-08:00" how can I parse this into NodaTime.LocalDateTime
or ZonedDateTime directly, rather than doing something like:
LocalDateTime.FromDateTime(DateTime.Parse("2020-12-15T12:10:00.202"))
And similarly for the other NodaTime types like LocalDate, Instant etc.
my end goal is to take the offset/timezone and change it to another
Help is appreciated.
I believe I need to work with modifying the pattern:
Var pattern = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd'T'HH:mm:ss.fff");
but I tried to add a z at the end for the timezone and that appears to be incorrect
even saw another post that mentions make it the full 9 digits:
static readonly ZonedDateTimePattern ParsePatternA =
ZonedDateTimePattern.CreateWithInvariantCulture("uuuu'-'MM'-'dd'T'HH':'mm':'ss.fffffffffz", DateTimeZoneProviders.Tzdb);
The value you've got there is logically an OffsetDateTime. Fortunately the pattern is simple ISO-8601, so you don't need to create any kind of custom pattern.
I'd strongly advise you to parse directly to the type that the value logically represents, then perform any conversions if you need to.
using NodaTime;
using NodaTime.Text;
string text = "2020-12-15T12:10:00.202-08:00";
OffsetDateTimePattern pattern = OffsetDateTimePattern.ExtendedIso;
ParseResult<OffsetDateTime> parseResult = pattern.Parse(text);
if (parseResult.Success)
{
OffsetDateTime value = parseResult.Value;
Console.WriteLine($"Successfully parsed: {value}");
// If you really need to convert to LocalDateTime...
LocalDateTime local = value.LocalDateTime;
Console.WriteLine($"Local value: {local}");
}
else
{
Console.WriteLine($"Unable to parse value: {parseResult.Exception.Message}");
}

Passing date as string - prevent json.net from parsing date

I have a Web API middle layer which consumes an API which exposes a field which carries a timestamp as string (the field is string and it contains a value like "2016-05-31T14:12:45.753Z").
The proxy classes in the middle tier are generated using Visual Studio from Swagger endpoint and under the hood the object is deserialized using Json.NET.
I can see that the field was received as string (that's good):
inputObject {{ "When": "2016-05-31T14:12:45.753Z" }} Newtonsoft.Json.Linq.JToken {Newtonsoft.Json.Linq.JObject}
However, even though the target field is string the value of inputObject["When"] is a parsed as a timestamp.
inputObject["When"] {31/05/2016 14:12:45} Newtonsoft.Json.Linq.JToken {Newtonsoft.Json.Linq.JValue}
Then
JToken whenValue = inputObject["When"];
if (whenValue != null && whenValue.Type != JTokenType.Null)
{
this.When = ((string)whenValue);
}
In the end this.When is a string with value 31/05/2016 14:12:45.
Is there an option to prevent json.net from parsing the date and then casting it to string again?
Please remember that this transformation happens in auto generated code so I'm looking for some way of decorating the field on the server side which would make Swagger mark it somehow and then the generated classes would avoid the deserialize/serialize issue.
Something like:
[JsonProperty("This really is a string, leave it alone")]
public string When { get; private set; }
(Answering my own question)
I needed a solution quickly and this is my temporary solution, for the record.
I format the date as
"When": "2016-05-31 14:12:45"
and not
"When": "2016-05-31T14:12:45.753Z"
This prevents it from being interpreted. The front end (javascript) code knows that timestamps from the API are UTC and it appends 'Z' before transforming the timestamp to local time and formatting for display, e.g:
<td>{{vm.prettyDateTimeFormat(item.StatusDate+'Z')}}</td>
The ctrl code:
vm.prettyDateTimeFormat = function (dateString)
{
var momentDate = moment(dateString, "YYYY-MM-DD HH:mm:ssZZ");
if (typeof(momentDate) === "undefined" || (!momentDate.isValid()))
{
return dateString;
}
//The format needs to be sortable as it ends up in the grid.
var nicePrettyDate = momentDate.format('YYYY-MM-DD HH:mm:ss');
return nicePrettyDate;
}
As far as I don't like this solution it carried us through the demo. This issue is obviously in the back log now to be addressed properly.
[JsonIgnore]
public string When { get; private set; }

DataTable to CSV date format

I'm trying to export a DataTable to CSV, with a number of columns. I would like that the date_time column is exported to CSV while retaining the database date_time format (YYY-MM-DD HH:mm:ss), or something like that.
This is my code:
private void DataTableToCsv(string path, DataTable dt)
{
File.Delete(path);
StringBuilder sb = new StringBuilder();
string[] columnNames = dt.Columns.Cast<DataColumn>().
Select(column => column.ColumnName).
ToArray();
sb.AppendLine(string.Join(",", columnNames));
foreach (DataRow row in dt.Rows)
{
string[] fields = row.ItemArray.Select(field => field.ToString()).
ToArray();
sb.AppendLine(string.Join(",", fields));
}
File.WriteAllText(path, sb.ToString());
}
The dates are appearing in a different format, which is giving me errors when trying to pick up from MySQL.
I know there is an accepted answer already, but...
Actually there is a way to control the formatting of dates in a consistent manner, without specifying the format for every type of data: you can use IFormatProvider
First, the method object.ToString(); with no parameters is formatting your object using current CultureInfo set in your thread/application/system. This is problematic when you need consistent formatting, for example when you try to save values into database. It isn't only problematic for dates but also for numbers - there are languages out there that use comma as decimal separator or use thousand separator in a number, for example, and databases do not like that very much. Now imagine your program is run on the computer that has a different language set...
Having said that, many of those problems can be avoided by using object.ToString(CultureInfo.InvariantCulture); instead. It is associated with the English language. Then, the result for 31th October 2014will be: 10/31/2014 08:35:52
Obviously still not what you want.
You can further control your formatting by modifying the culture a little yourself:
CultureInfo format = (CultureInfo) CultureInfo.InvariantCulture.Clone();
// those are the formats used by ToString() mathod:
format.DateTimeFormat.ShortDatePattern = "yyyy-MM-dd";
format.DateTimeFormat.ShortTimePattern = "HH:mm:ss";
Console.WriteLine(DateTime.Now.ToString(format));
double ddd=13.11;
Console.WriteLine(ddd.ToString(format));
string sss = "some string";
// although this may not be a good idea:
Console.WriteLine(sss.ToString(format));
result:
2014-10-31 08:39:53
13.11
some string
With DateTime values, you can control the output by calling
dateTimeField.ToString("yyyy-dd-MM");
Currently you're treating all fields equally, and using the default conversion:
field => field.ToString()
You can't specify a conversion as object.ToString() has no overloads to do so; you'd have to specify what conversion to use for each type separately, ie:
string[] fields = row.ItemArray.Select(field => ConvertToCsvValue(field)).ToArray();
and
static void ConvertToCsvValue(object value)
{
if (value is DateTime)
{
return ((DateTime)value).ToString("yyyy-dd-MM");
}
else
{
return value.ToString();
}
}
data in a database is not aware of any format; the behaviour you are experiencing is related to that and is correct.
when filling the csv your code is taking the default format for the env/os/locale and applying it to the values.
to solve the issue you have some possible solution:
you change the source sql code to produce a sql text field filled with formatted data (imho a bad choice)
you handle the format client side when preparing the csv; you have to modify your code to handle the fields one by one applying the correct format to date fields
you alter the datatable content applying the expected format to date fields (this may be possible or maybe not depending on datatype of the involved fields)
my preference goes to the second or third solution because i prefer to keep formatting on the client side but YMMV and in your post there is not enough information to make a proper choice.

How can I format string values using a mask in a generic way?

Background: We have a system that receives data from another backend system. We handle the displaying of that data, and we have our own XML templates to control how certain things are displayed (i.e. we have our own column templates to dictate what the column headers are, etc.) One thing we would like to support is the ability to provide a mask for these column templates that would apply to the values coming from the backend. Below is a scenario that I'm having trouble with.
Problem: I can't seem to get a simple string format working. I'd like to format a STRING value of four digits (i.e. "1444") in a time format (i.e. "14:44"). I've tried:
String.Format("{0:00:00}", "1444")
Note the importance of the input being a STRING. If I supply an int value, the format will work. The reason I cannot use this is because all the data we receive from the backend is in string format, and we'd like for this to be generic (so casting isn't really an option).
By generic, I mean I'd like to specify a mask in our own XML templates, something like:
<MyColumnTemplate Id="MyColumn" Mask="00:00" />
and to use that mask in a string format call for string values? If the mask fails, we could just simply return the original value (as the String.Format() method already does by default).
Edit: To help clarify, here is a simplified version of what I'd like to be able to do in code:
string inputValue = "1444";
string maskValueFromXml = "00:00";
string mask = "{0:" + maskValueFromXml + "}";
string myDesiredEndResult = String.Format(mask, inputValue);
The thing is you are working string to string,since you ask for time and phone number they are all numbers then try this trick(if we can call it that :)):
string result = string.Format("{0:00:00}", int.Parse("1444"));
For phone number:
string result = string.Format("{0:000-000-0000}", int.Parse("1234560789"));
You can even place your desired masks in a dictionary for example and do this:
Dictionary<string, string> masks = new Dictionary<string, string>();
masks.Add("Phone", "{0:000-000-0000}");
masks.Add("Time", "{0:00:00}");
string test = "1234560789";
string result = string.Format(masks["Phone"], int.Parse(test));
Try with DateTime.TryParseExact, e.g:
DateTime dateEntered;
string input = "1444";
if (DateTime.TryParseExact(input, "HH:mm", System.Globalization.CultureInfo.CurrentCulture, System.Globalization.DateTimeStyles.None, out dateEntered))
{
MessageBox.Show(dateEntered.ToString());
}
else
{
MessageBox.Show("You need to enter valid 24hr time");
}
After that, you can use string.Format, predefined formats on MSDN.

C#: Extract number out of string, then change comma(,) to dot(.)

I'm using Visual Web Ripper to extract name and prices on products on a website.
When i extract the price from a table it comes in a form like this:
Kr. 129,30
I need to extract the 129,30, then turn the comma to a dot (129.30).
Visual Web Ripper can use scripts to modify the extracted content. It can use standard Regex, C# and VB.NET.
In the Regex tab I have found that
(\d+.)?(\d+)(.\d+)?
gives me 129,30, but then I can't change the comma into a dot.
Therefor I have to use C#. It comes with this standard script:
using System;
using VisualWebRipper.Internal.SimpleHtmlParser;
using VisualWebRipper;
public class Script
{
//See help for a definition of WrContentTransformationArguments.
public static string TransformContent(WrContentTransformationArguments args)
{
try
{
//Place your transformation code here.
//This example just returns the input data
return args.Content;
}
catch(Exception exp)
{
//Place error handling here
args.WriteDebug("Custom script error: " + exp.Message);
return "Custom script error";
}
}
}
How do I modify it to extract the number then replace the comma with a dot?
This is obviously Krona, so we should use the Swedish culture info to translate it. First we start with the input:
var original = "Kr. 129,30";
Get the culture:
using System.Globalization;
var culture = CultureInfo.GetCultureInfo("sv-SE");
This culture expects the currency string to be kr (case insensitive) but we have Kr.. So let's update it:
var format = (NumberFormatInfo)culture.NumberFormat.Clone();
format.CurrencySymbol = "Kr.";
And now the culture aware parse:
var number = Decimal.Parse(original, NumberStyles.Currency, format);
Now number contains a decimal that has been parsed correctly.
String.Replace is an option ( text.Replace(",", ".")).
It would be better to properly parse number with correct CultureInfo and than reformat it back with InvariantCulture.

Categories