V1.0.1.8 -- 2025/12/20
* BMS History Function Improved
This commit is contained in:
@@ -1,16 +1,19 @@
|
||||
using System;
|
||||
using ClosedXML.Excel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.OleDb;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Data;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using System.Data.OleDb;
|
||||
|
||||
namespace LFP_Manager.Function
|
||||
{
|
||||
public class csExcelFunction
|
||||
{
|
||||
#region EXCEL IMPORT / EXPORT
|
||||
public static DataTable[] ExcelImport(string Ps_FileName, string[] sheetName)
|
||||
{
|
||||
DataTable[] result = null;
|
||||
@@ -141,5 +144,209 @@ namespace LFP_Manager.Function
|
||||
connection.Dispose();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region EXCEL CLOSED XML IMPORT / EXPORT
|
||||
|
||||
private static readonly string[] IsoDateFormats = new string[]
|
||||
{
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
"yyyy-MM-ddTHH:mm:ss",
|
||||
"yyyy-MM-ddTHH:mm:ss.fff",
|
||||
"yyyy-MM-ddTHH:mm:ssZ",
|
||||
"yyyy-MM-ddTHH:mm:ss.fffZ",
|
||||
"yyyy-MM-dd"
|
||||
};
|
||||
|
||||
public static bool ExportDgvWithClosedXml(string filePath, DataTable[] dt, bool overwiteFile = true)
|
||||
{
|
||||
if (dt == null || dt.Length == 0) return false;
|
||||
|
||||
// .xls로 들어오면 .xlsx로 변환하여 저장 경로 결정
|
||||
var outputPath = EnsureXlsxPath(filePath);
|
||||
|
||||
// 저장 경로 폴더 보장
|
||||
var dir = Path.GetDirectoryName(outputPath);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
if (File.Exists(outputPath))
|
||||
{
|
||||
if (overwiteFile) File.Delete(outputPath);
|
||||
else throw new IOException("File already exists: " + outputPath);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var wb = new XLWorkbook()) // C# 7.3 호환
|
||||
{
|
||||
var nameCount = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
for (int i = 0; i < dt.Length; i++)
|
||||
{
|
||||
var table = dt[i];
|
||||
if (table == null) continue;
|
||||
|
||||
string baseName = !string.IsNullOrWhiteSpace(table.TableName) ? table.TableName : ("Sheet" + (i + 1));
|
||||
string sheetName = SanitizeSheetName(baseName);
|
||||
|
||||
// 중복 시트명 처리: _2, _3...
|
||||
int dup;
|
||||
if (nameCount.TryGetValue(sheetName, out dup))
|
||||
{
|
||||
dup++;
|
||||
nameCount[sheetName] = dup;
|
||||
string prefix = sheetName.Length > 28 ? sheetName.Substring(0, 28) : sheetName; // "_n" 자리 고려
|
||||
sheetName = prefix + "_" + dup;
|
||||
}
|
||||
else
|
||||
{
|
||||
nameCount[sheetName] = 1;
|
||||
}
|
||||
|
||||
// DataTable 전체를 시트로 추가 (헤더 포함)
|
||||
var ws = wb.Worksheets.Add(table, sheetName);
|
||||
|
||||
// 자동 필터
|
||||
var tbl = ws.Tables.FirstOrDefault();
|
||||
if (tbl != null) tbl.ShowAutoFilter = true;
|
||||
|
||||
// ====== [추가] 문자열 날짜 → 진짜 DateTime으로 변환 ======
|
||||
// 1) 날짜 컬럼 후보 탐지 (타입/컬럼명/샘플링)
|
||||
bool[] isDateCol = new bool[table.Columns.Count];
|
||||
for (int c = 0; c < table.Columns.Count; c++)
|
||||
{
|
||||
var colType = table.Columns[c].DataType;
|
||||
if (colType == typeof(DateTime)) { isDateCol[c] = true; continue; }
|
||||
|
||||
if (colType == typeof(string))
|
||||
{
|
||||
string colName = table.Columns[c].ColumnName ?? "";
|
||||
// 컬럼명 힌트
|
||||
if (colName.IndexOf("date", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
colName.IndexOf("time", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
colName.EndsWith("_at", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
isDateCol[c] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 값 샘플링 (최대 20개)
|
||||
int seen = 0, hits = 0;
|
||||
for (int r = 0; r < table.Rows.Count && seen < 20; r++)
|
||||
{
|
||||
var obj = table.Rows[r][c];
|
||||
if (obj == null || obj == DBNull.Value) continue;
|
||||
var s = obj.ToString();
|
||||
if (string.IsNullOrWhiteSpace(s)) continue;
|
||||
seen++;
|
||||
DateTime tmp;
|
||||
if (TryParseIsoDate(s, out tmp)) hits++;
|
||||
}
|
||||
if (seen > 0 && (hits * 1.0 / seen) >= 0.6) isDateCol[c] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 열 서식 지정 + 각 셀 값 DateTime으로 재설정
|
||||
for (int c = 0; c < table.Columns.Count; c++)
|
||||
{
|
||||
var col = ws.Column(c + 1);
|
||||
Type t = table.Columns[c].DataType;
|
||||
|
||||
if (isDateCol[c])
|
||||
{
|
||||
// 열 서식(표시): 날짜/시간
|
||||
col.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
// 데이터 행(2행부터) 변환
|
||||
int rowCount = table.Rows.Count;
|
||||
for (int r = 0; r < rowCount; r++)
|
||||
{
|
||||
var cell = ws.Cell(r + 2, c + 1);
|
||||
|
||||
// 이미 DateTime 셀은 스킵
|
||||
if (cell.DataType == ClosedXML.Excel.XLDataType.DateTime)
|
||||
continue;
|
||||
|
||||
var s = cell.GetString();
|
||||
if (string.IsNullOrWhiteSpace(s)) continue;
|
||||
|
||||
DateTime dtv;
|
||||
if (TryParseIsoDate(s, out dtv))
|
||||
{
|
||||
cell.Value = dtv; // ✅ 진짜 날짜로 저장 (엑셀 DateTime)
|
||||
// cell.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss"; // per-cell 필요시
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 기존 숫자/텍스트 서식 유지
|
||||
if (t == typeof(DateTime))
|
||||
col.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss";
|
||||
else if (t == typeof(int) || t == typeof(long) || t == typeof(short) || t == typeof(byte))
|
||||
col.Style.NumberFormat.Format = "0"; // 정수
|
||||
else if (t == typeof(decimal) || t == typeof(float) || t == typeof(double))
|
||||
col.Style.NumberFormat.Format = "General"; // 일반 숫자
|
||||
else
|
||||
col.Style.NumberFormat.Format = "@"; // 텍스트
|
||||
}
|
||||
}
|
||||
// ====== [추가 끝] ======
|
||||
|
||||
// 열 너비 자동 조정
|
||||
ws.Columns().AdjustToContents();
|
||||
}
|
||||
wb.SaveAs(outputPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 필요 시 로깅 추가
|
||||
throw new Exception(ex.Message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string EnsureXlsxPath(string filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException(nameof(filePath));
|
||||
var ext = Path.GetExtension(filePath);
|
||||
if (!string.IsNullOrEmpty(ext) && ext.Equals(".xls", StringComparison.OrdinalIgnoreCase))
|
||||
return Path.ChangeExtension(filePath, ".xlsx");
|
||||
return filePath;
|
||||
}
|
||||
|
||||
private static string SanitizeSheetName(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return "Sheet1";
|
||||
// Excel 금지문자: : \ / ? * [ ]
|
||||
char[] invalid = { ':', '\\', '/', '?', '*', '[', ']' };
|
||||
var arr = name.ToCharArray();
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
for (int j = 0; j < invalid.Length; j++)
|
||||
if (arr[i] == invalid[j]) { arr[i] = '_'; break; }
|
||||
|
||||
var sanitized = new string(arr);
|
||||
if (sanitized.Length > 31) sanitized = sanitized.Substring(0, 31);
|
||||
if (string.IsNullOrWhiteSpace(sanitized)) sanitized = "Sheet1";
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
private static bool TryParseIsoDate(string s, out DateTime dt)
|
||||
{
|
||||
// 로컬 시간 가정(오프셋 보정 없음). DB 문자열이 UTC라면 AdjustToUniversal로 변경하세요.
|
||||
return DateTime.TryParseExact(
|
||||
s, IsoDateFormats,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeLocal,
|
||||
out dt)
|
||||
|| DateTime.TryParse(
|
||||
s, CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeLocal,
|
||||
out dt);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user