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.Windows.Forms; namespace LFP_Manager.Function { public class csExcelFunction { #region EXCEL IMPORT / EXPORT public static DataTable[] ExcelImport(string Ps_FileName, string[] sheetName) { DataTable[] result = null; try { string ExcelConn = string.Empty; if (Ps_FileName.IndexOf(".xlsx") > -1) // 확장자에 따라서 provider 주의 { ExcelConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + Ps_FileName + ";Extended Properties='Excel 12.0;HDR=YES'"; } else { ExcelConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Ps_FileName + ";Extended Properties='Excel 8.0;HDR=YES'"; } // 첫 번째 시트의 이름을 가져옮 using (OleDbConnection con = new OleDbConnection(ExcelConn)) { using (OleDbCommand cmd = new OleDbCommand()) { cmd.Connection = con; con.Open(); DataTable dtExcelSchema = con.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null); if (dtExcelSchema.Rows.Count > 0) { sheetName = new string[dtExcelSchema.Rows.Count]; for (int i = 0; i < dtExcelSchema.Rows.Count; i++) sheetName[i] = dtExcelSchema.Rows[i]["TABLE_NAME"].ToString(); } con.Close(); } } string msg = string.Empty; for (int i = 0; i < sheetName.Length; i++) msg += sheetName[i] + "\r\n"; //MessageBox.Show("sheetName = " + msg); // 첫 번째 쉬트의 데이타를 읽어서 datagridview 에 보이게 함. using (OleDbConnection con = new OleDbConnection(ExcelConn)) { using (OleDbCommand cmd = new OleDbCommand()) { using (OleDbDataAdapter oda = new OleDbDataAdapter()) { result = new DataTable[sheetName.Length]; for (int i = 0; i < sheetName.Length; i++) { result[i] = new DataTable(); result[i].TableName = sheetName[i]; cmd.CommandText = "SELECT * From [" + sheetName[i] + "]"; cmd.Connection = con; con.Open(); oda.SelectCommand = cmd; try { oda.Fill(result[i]); } catch (Exception) { //MessageBox.Show(e1.Message); } con.Close(); } } } } } catch (Exception e) { MessageBox.Show(e.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error); } return result; } public static void ExcelExport(string Ps_FileName, string[] sheetName) { string ExcelConn = string.Empty; if (Ps_FileName.IndexOf(".xlsx") > -1) // 확장자에 따라서 provider 주의 { ExcelConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + Ps_FileName + ";Extended Properties='Excel 12.0;HDR=YES;IMEX=3;READONLY=FALSE'"; } else { ExcelConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Ps_FileName + ";Extended Properties='Excel 8.0;HDR=YES;IMEX=3;READONLY=FALSE'"; } // 첫 번째 시트의 이름을 가져옮 using (OleDbConnection con = new OleDbConnection(ExcelConn)) { using (OleDbCommand cmd = new OleDbCommand()) { cmd.Connection = con; con.Open(); DataTable dtExcelSchema = con.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null); if (dtExcelSchema.Rows.Count > 0) { sheetName = new string[dtExcelSchema.Rows.Count]; for (int i = 0; i < dtExcelSchema.Rows.Count; i++) sheetName[i] = dtExcelSchema.Rows[i]["TABLE_NAME"].ToString(); } con.Close(); } } using (OleDbConnection connection = new OleDbConnection(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + @"C:\Users\[...]\Classeur.xls" + ";Extended Properties=\"Excel 8.0;HDR=NO;IMEX=1;READONLY=FALSE\"")) { connection.Open(); OleDbCommand commande = new OleDbCommand( "INSERT INTO [Feuil1$](F1,F2,F3) VALUES ('A3','B3','C3');", connection); commande.ExecuteNonQuery(); connection.Close(); 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(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 } }