MUSX Document Model
Loading...
Searching...
No Matches
DateTimeFormat.h
1/*
2 * Copyright (C) 2025, Robert Patterson
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22#pragma once
23
24#include <string>
25#include <cstring>
26#include <ctime>
27
28#ifdef MUSX_RUNNING_ON_WINDOWS
29#include <windows.h>
30#else
31#include <locale>
32#include <sstream>
33#include <iomanip>
34#include <langinfo.h>
35#endif
36
37#include "musx/dom/EnumClasses.h"
38
39namespace musx {
40namespace util {
41
47{
49
58 inline static std::time_t makeTimeT(int year, int month, int day)
59 {
60 std::tm tm = {};
61 tm.tm_year = year - 1900; // tm_year is years since 1900
62 tm.tm_mon = month - 1; // tm_mon is 0-based
63 tm.tm_mday = day;
64 tm.tm_hour = 0;
65 tm.tm_min = 0;
66 tm.tm_sec = 0;
67 tm.tm_isdst = -1; // Let system determine daylight saving time
68
69 return std::mktime(&tm);
70 }
71
72#ifdef MUSX_RUNNING_ON_WINDOWS
82 inline static std::string formatDate(std::time_t t, DateFormat style)
83 {
84 SYSTEMTIME st;
85 {
86 struct tm tm;
87 localtime_s(&tm, &t);
88 st.wYear = WORD(tm.tm_year + 1900);
89 st.wMonth = WORD(tm.tm_mon + 1);
90 st.wDay = WORD(tm.tm_mday);
91 st.wHour = WORD(tm.tm_hour);
92 st.wMinute = WORD(tm.tm_min);
93 st.wSecond = WORD(tm.tm_sec);
94 st.wMilliseconds = 0;
95 }
96
97 // Determine flags and custom format string
98 DWORD flags = 0;
99 const wchar_t* customFormat = nullptr;
100
101 switch (style)
102 {
103 case DateFormat::Short:
104 flags = DATE_SHORTDATE;
105 break;
106
107 case DateFormat::Long:
108 flags = DATE_LONGDATE;
109 break;
110
111 case DateFormat::Abbrev:
112 // Windows does not provide a built-in long abbreviated date, so use custom format string
113 customFormat = L"MMM d, yyyy";
114 break;
115 }
116
117 // First call to get required size
118 int requiredSize = GetDateFormatEx(
119 nullptr, flags, &st, customFormat, nullptr, 0, nullptr);
120
121 if (requiredSize == 0) {
122 throw std::system_error(GetLastError(), std::system_category());
123 }
124
125 // Allocate WCHAR buffer with exact required size
126 std::wstring wbuf(requiredSize, L'\0');
127
128 // Second call to actually format
129 int result = GetDateFormatEx(
130 nullptr, flags, &st, customFormat, wbuf.data(), requiredSize, nullptr);
131
132 if (result == 0) {
133 throw std::system_error(GetLastError(), std::system_category());
134 }
135
136 // Convert WCHAR (UTF-16) to UTF-8
137 int utf8len = WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, nullptr, 0, nullptr, nullptr);
138 std::string output(utf8len, '\0');
139 WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, &output[0], utf8len, nullptr, nullptr);
140 output.resize(utf8len - 1); // remove null terminator
141
142 return output;
143 }
144#endif // MUSX_RUNNING_ON_WINDOWS
145
146#ifndef MUSX_RUNNING_ON_WINDOWS
156 inline static std::string formatDate(std::time_t t, DateFormat style)
157 {
158 struct tm tm;
159
160 localtime_r(&t, &tm);
161
162 const char* fmt = nullptr;
163 switch (style) {
164 case DateFormat::Short:
165 fmt = "%x";
166 break;
167 case DateFormat::Long:
168 fmt = "%B %d, %Y";
169 break;
170 case DateFormat::Abbrev:
171 fmt = "%b %d, %Y";
172 break;
173 }
174
175 std::ostringstream oss;
176 oss.imbue(std::locale("")); // Use user’s current locale
177
178 oss << std::put_time(&tm, fmt);
179 return oss.str();
180 }
181#endif // !MUSX_RUNNING_ON_WINDOWS
182
193 inline static std::string formatTime(std::time_t t, bool includeSeconds)
194 {
195#ifdef MUSX_RUNNING_ON_WINDOWS
196 SYSTEMTIME st;
197 {
198 struct tm tm;
199 localtime_s(&tm, &t);
200 st.wYear = WORD(tm.tm_year + 1900);
201 st.wMonth = WORD(tm.tm_mon + 1);
202 st.wDay = WORD(tm.tm_mday);
203 st.wHour = WORD(tm.tm_hour);
204 st.wMinute = WORD(tm.tm_min);
205 st.wSecond = WORD(tm.tm_sec);
206 st.wMilliseconds = 0;
207 }
208
209 DWORD flags = 0;
210 if (!includeSeconds) {
211 flags = TIME_NOSECONDS;
212 }
213
214 // First call to get required size
215 int requiredSize = GetTimeFormatEx(
216 nullptr, flags, &st, nullptr, nullptr, 0);
217
218 if (requiredSize == 0) {
219 throw std::system_error(GetLastError(), std::system_category());
220 }
221
222 // Allocate WCHAR buffer
223 std::wstring wbuf(requiredSize, L'\0');
224
225 // Second call to actually format
226 int result = GetTimeFormatEx(
227 nullptr, flags, &st, nullptr, wbuf.data(), requiredSize);
228
229 if (result == 0) {
230 throw std::system_error(GetLastError(), std::system_category());
231 }
232
233 // Convert WCHAR (UTF-16) to UTF-8
234 int utf8len = WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, nullptr, 0, nullptr, nullptr);
235 std::string output(utf8len, '\0');
236 WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, &output[0], utf8len, nullptr, nullptr);
237 output.resize(utf8len - 1); // remove null terminator
238
239 return output;
240
241#else // POSIX
242
243 struct tm tm;
244 localtime_r(&t, &tm);
245
246 const char* fmt = nullptr;
247
248 if (includeSeconds) {
249 fmt = "%X"; // Locale-specific full time with seconds
250 } else {
251 // Determine 24h vs 12h heuristic using nl_langinfo
252 bool is24h = false;
253 const char* tfmt = nl_langinfo(T_FMT);
254 if (tfmt && std::strchr(tfmt, 'H')) {
255 is24h = true;
256 }
257
258 fmt = is24h ? "%H:%M" : "%I:%M %p";
259 }
260
261 std::ostringstream oss;
262 oss.imbue(std::locale("")); // Use user's current locale
263 oss << std::put_time(&tm, fmt);
264 return oss.str();
265
266#endif
267 }
268
269};
270
271} // namespace util
272} // namespace musx
DateFormat
Date format options. This value is coded into the Enigma date insert when the page title is created.
Definition EnumClasses.h:48
object model for musx file (enigmaxml)
Definition BaseClasses.h:36
Static class to provide utility functions for formatting date and time.
Definition DateTimeFormat.h:47
static std::time_t makeTimeT(int year, int month, int day)
Creates a std::time_t from integer year, month, and day.
Definition DateTimeFormat.h:58
static std::string formatDate(std::time_t t, DateFormat style)
Formats a date according to the current locale on POSIX systems.
Definition DateTimeFormat.h:156
musx::dom::DateFormat DateFormat
Date format options.
Definition DateTimeFormat.h:48
static std::string formatTime(std::time_t t, bool includeSeconds)
Formats a time according to the current locale.
Definition DateTimeFormat.h:193