Loading commands/system/date.cpp +236 −18 Changes for commands/system/date.cpp: 236 added lines, 18 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2022, Jan Koester jan.koester@gmx.net Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without Loading @@ -25,44 +25,262 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <cstring> #include <cstdlib> #include <ctime> #include <cctype> #include <cerrno> #include <iostream> #include <string> #include <sys/stat.h> #include <sys/time.h> #include "shell.h" namespace fresh { namespace cmd { // date [-u] [-d STRING|-r FILE] [-R|-I[FMT]] [-s STRING] [+FORMAT] // -d understands @EPOCH, YYYY-MM-DD[ HH:MM[:SS]] (also with T, Z or a // +HH:MM offset), MM/DD/YYYY, now/today/yesterday/tomorrow and relative // parts ("2 days ago", "+3 hours", "next week" ...), combined freely. class DateCommand : public ShellCommand { public: DateCommand():ShellCommand("date"){} private: static std::string lower(std::string s){ for(auto &c: s) c=(char)tolower((unsigned char)c); return s; } // parses s relative to base; false if something is left over static bool parseDate(const std::string &str,bool utc,struct timespec &ts){ std::string s=lower(str); struct timespec now; clock_gettime(CLOCK_REALTIME,&now); time_t t=now.tv_sec; long nsec=now.tv_nsec; struct tm tmv; if(utc) gmtime_r(&t,&tmv); else localtime_r(&t,&tmv); bool haveDate=false,haveTime=false,absolute=false; long offsetSecs=0; bool haveOffset=false; long long rel[6]={0,0,0,0,0,0}; // years months days hours minutes seconds size_t p=0; auto ws=[&](){ while(p<s.size() && (isspace((unsigned char)s[p]) || s[p]==',')) ++p; }; ws(); if(p<s.size() && s[p]=='@'){ char *end; double v=strtod(s.c_str()+p+1,&end); if(end==s.c_str()+p+1) return false; ts.tv_sec=(time_t)v; ts.tv_nsec=(long)((v-(double)(time_t)v)*1e9); p=end-s.c_str(); ws(); return p==s.size(); } while(p<s.size()){ ws(); if(p>=s.size()) break; int y,mo,d,h,mi,sec; char c1; int n=0; if(sscanf(s.c_str()+p,"%d-%d-%d%n",&y,&mo,&d,&n)==3 && n>0 && !haveDate){ tmv.tm_year=y-1900; tmv.tm_mon=mo-1; tmv.tm_mday=d; tmv.tm_hour=tmv.tm_min=tmv.tm_sec=0; nsec=0; haveDate=absolute=true; p+=n; if(p<s.size() && s[p]=='t') ++p; continue; } n=0; if(sscanf(s.c_str()+p,"%d/%d/%d%n",&mo,&d,&y,&n)==3 && n>0 && !haveDate){ tmv.tm_year=y-1900; tmv.tm_mon=mo-1; tmv.tm_mday=d; tmv.tm_hour=tmv.tm_min=tmv.tm_sec=0; nsec=0; haveDate=absolute=true; p+=n; continue; } n=0; sec=0; if((sscanf(s.c_str()+p,"%d:%d:%d%n",&h,&mi,&sec,&n)==3 && n>0) || (n=0,sscanf(s.c_str()+p,"%d:%d%n",&h,&mi,&n)==2 && n>0)){ if(haveTime) return false; tmv.tm_hour=h; tmv.tm_min=mi; tmv.tm_sec=sec; nsec=0; haveTime=absolute=true; p+=n; if(p<s.size() && s[p]=='.'){ ++p; while(p<s.size() && isdigit((unsigned char)s[p])) ++p; } continue; } if(s[p]=='z' && (p+1==s.size() || isspace((unsigned char)s[p+1]))){ haveOffset=true; offsetSecs=0; ++p; continue; } if((s[p]=='+' || s[p]=='-') && haveTime && !haveOffset){ int oh=0,om=0; n=0; if(sscanf(s.c_str()+p+1,"%2d:%2d%n",&oh,&om,&n)>=2 || sscanf(s.c_str()+p+1,"%2d%2d%n",&oh,&om,&n)>=2){ offsetSecs=(oh*3600+om*60)*(s[p]=='-' ? -1 : 1); haveOffset=true; p+=1+n; continue; } } // words and relative items size_t q=p; std::string word; long long num=1; bool haveNum=false; if(s[q]=='+' || s[q]=='-' || isdigit((unsigned char)s[q])){ char *end; num=strtoll(s.c_str()+q,&end,10); if(end==s.c_str()+q) return false; haveNum=true; q=end-s.c_str(); while(q<s.size() && isspace((unsigned char)s[q])) ++q; } size_t ws0=q; while(q<s.size() && isalpha((unsigned char)s[q])) ++q; word=s.substr(ws0,q-ws0); if(word.empty()) return false; p=q; if(!haveNum){ if(word=="now"){ continue; } if(word=="today"){ continue; } if(word=="yesterday"){ rel[2]-=1; continue; } if(word=="tomorrow"){ rel[2]+=1; continue; } if(word=="next"){ num=1; } else if(word=="last"){ num=-1; } else if(word=="this"){ num=0; } else if(word=="ago"){ for(auto &r: rel) r=-r; continue; } else if(word=="utc" || word=="gmt"){ haveOffset=true; offsetSecs=0; continue; } else{ num=1; p=ws0; } if(word=="next" || word=="last" || word=="this"){ ws(); ws0=p; while(p<s.size() && isalpha((unsigned char)s[p])) ++p; word=s.substr(ws0,p-ws0); }else{ while(p<s.size() && isalpha((unsigned char)s[p])) ++p; } } if(!word.empty() && word.back()=='s' && word!="s") word.pop_back(); if(word=="year") rel[0]+=num; else if(word=="month") rel[1]+=num; else if(word=="fortnight") rel[2]+=14*num; else if(word=="week") rel[2]+=7*num; else if(word=="day") rel[2]+=num; else if(word=="hour") rel[3]+=num; else if(word=="minute" || word=="min") rel[4]+=num; else if(word=="second" || word=="sec") rel[5]+=num; else return false; } tmv.tm_year+=(int)rel[0]; tmv.tm_mon+=(int)rel[1]; tmv.tm_mday+=(int)rel[2]; tmv.tm_isdst=-1; time_t r; if(haveOffset){ r=timegm(&tmv)-offsetSecs; }else{ r=utc ? timegm(&tmv) : mktime(&tmv); } r+=(time_t)(rel[3]*3600+rel[4]*60+rel[5]); ts.tv_sec=r; ts.tv_nsec=absolute ? 0 : nsec; return true; } // strftime plus %N (nanoseconds) and the %-/%_/%0 padding flags static std::string format(const std::string &fmt,const struct timespec &ts,bool utc){ struct tm tmv; if(utc) gmtime_r(&ts.tv_sec,&tmv); else localtime_r(&ts.tv_sec,&tmv); std::string out; for(size_t i=0;i<fmt.size();++i){ if(fmt[i]!='%' || i+1>=fmt.size()){ out+=fmt[i]; continue; } char flag=0; size_t j=i+1; if(fmt[j]=='-' || fmt[j]=='_' || fmt[j]=='0' || fmt[j]=='^'){ flag=fmt[j]; ++j; } if(j>=fmt.size()){ out+=fmt.substr(i); break; } char conv=fmt[j]; std::string piece; if(conv=='N'){ char b[16]; snprintf(b,sizeof b,"%09ld",ts.tv_nsec); piece=b; }else if(conv=='s'){ piece=std::to_string((long long)ts.tv_sec); }else if(conv=='P'){ piece=tmv.tm_hour<12 ? "am" : "pm"; }else{ char f[3]={'%',conv,0}; char b[256]; size_t n=strftime(b,sizeof b,f,&tmv); piece.assign(b,n); } if(flag=='-'){ size_t z=0; while(z+1<piece.size() && (piece[z]=='0' || piece[z]==' ')) ++z; piece=piece.substr(z); } else if(flag=='_'){ for(size_t z=0;z+1<piece.size() && piece[z]=='0';++z) piece[z]=' '; } else if(flag=='0'){ for(size_t z=0;z+1<piece.size() && piece[z]==' ';++z) piece[z]='0'; } else if(flag=='^'){ for(auto &c: piece) c=(char)toupper((unsigned char)c); } out+=piece; i=j; } return out; } public: int runCommand(int argc,const char **argv){ time_t now=time(NULL); struct tm local; if(localtime_r(&now,&local)==NULL){ std::cerr << "date: failed to get local time" << std::endl; bool utc=false; std::string dateStr,refFile,setStr,fmt; bool haveDate=false,haveFmt=false; std::string iso; bool rfc=false; for(int i=1;i<argc;++i){ std::string a=argv[i]; auto need=[&](std::string &dst)->bool{ size_t eq=a.find('='); if(a.compare(0,2,"--")==0 && eq!=std::string::npos){ dst=a.substr(eq+1); return true; } if(i+1>=argc){ std::cerr << "date: option '" << a << "' requires an argument" << std::endl; return false; } dst=argv[++i]; return true; }; if(a[0]=='+'){ fmt=a.substr(1); haveFmt=true; continue; } if(a=="-u" || a=="--utc" || a=="--universal"){ utc=true; continue; } if(a=="-R" || a=="--rfc-2822" || a=="--rfc-email"){ rfc=true; continue; } if(a.compare(0,2,"-I")==0){ iso=a.size()>2 ? a.substr(2) : "date"; continue; } if(a.compare(0,10,"--iso-8601")==0){ iso=a.size()>11 ? a.substr(11) : "date"; continue; } if(a.compare(0,2,"-d")==0 && a.size()>2){ dateStr=a.substr(2); haveDate=true; continue; } if(a=="-d" || a.compare(0,6,"--date")==0){ if(!need(dateStr)) return 1; haveDate=true; continue; } if(a=="-r" || a.compare(0,11,"--reference")==0){ if(!need(refFile)) return 1; continue; } if(a=="-s" || a.compare(0,5,"--set")==0){ if(!need(setStr)) return 1; continue; } std::cerr << "date: invalid argument '" << a << "'" << std::endl; return 1; } char buf[256]; if(argc>1 && argv[1][0]=='+'){ const char *fmt=argv[1]+1; size_t n=strftime(buf,sizeof(buf),fmt,&local); if(n==0 && fmt[0]!='\0'){ std::cerr << "date: invalid format" << std::endl; struct timespec ts; clock_gettime(CLOCK_REALTIME,&ts); if(!refFile.empty()){ struct stat st; if(stat(refFile.c_str(),&st)!=0){ std::cerr << "date: " << refFile << ": " << strerror(errno) << std::endl; return 1; } ts=st.st_mtim; } if(haveDate && !parseDate(dateStr,utc,ts)){ std::cerr << "date: invalid date '" << dateStr << "'" << std::endl; return 1; } std::cout << buf << std::endl; return 0; if(!setStr.empty()){ if(!parseDate(setStr,utc,ts)){ std::cerr << "date: invalid date '" << setStr << "'" << std::endl; return 1; } if(clock_settime(CLOCK_REALTIME,&ts)!=0){ std::cerr << "date: cannot set date: " << strerror(errno) << std::endl; return 1; } } if(strftime(buf,sizeof(buf),"%a %b %e %H:%M:%S %Z %Y",&local)==0){ std::cerr << "date: failed to format time" << std::endl; return 1; std::string f; if(haveFmt) f=fmt; else if(rfc) f="%a, %d %b %Y %H:%M:%S %z"; else if(!iso.empty()){ if(iso=="date") f="%Y-%m-%d"; else if(iso=="hours") f="%Y-%m-%dT%H%:z"; else if(iso=="minutes") f="%Y-%m-%dT%H:%M%:z"; else if(iso=="ns") f="%Y-%m-%dT%H:%M:%S,%N%:z"; else f="%Y-%m-%dT%H:%M:%S%:z"; } else f="%a %b %e %H:%M:%S %Z %Y"; // %:z (+hh:mm) size_t cz; while((cz=f.find("%:z"))!=std::string::npos){ std::string z=format("%z",ts,utc); if(z.size()==5) z=z.substr(0,3)+":"+z.substr(3); f.replace(cz,3,z); } std::cout << buf << std::endl; std::cout << format(f,ts,utc) << std::endl; return 0; } }; Loading Loading
commands/system/date.cpp +236 −18 Changes for commands/system/date.cpp: 236 added lines, 18 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2022, Jan Koester jan.koester@gmx.net Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without Loading @@ -25,44 +25,262 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <cstring> #include <cstdlib> #include <ctime> #include <cctype> #include <cerrno> #include <iostream> #include <string> #include <sys/stat.h> #include <sys/time.h> #include "shell.h" namespace fresh { namespace cmd { // date [-u] [-d STRING|-r FILE] [-R|-I[FMT]] [-s STRING] [+FORMAT] // -d understands @EPOCH, YYYY-MM-DD[ HH:MM[:SS]] (also with T, Z or a // +HH:MM offset), MM/DD/YYYY, now/today/yesterday/tomorrow and relative // parts ("2 days ago", "+3 hours", "next week" ...), combined freely. class DateCommand : public ShellCommand { public: DateCommand():ShellCommand("date"){} private: static std::string lower(std::string s){ for(auto &c: s) c=(char)tolower((unsigned char)c); return s; } // parses s relative to base; false if something is left over static bool parseDate(const std::string &str,bool utc,struct timespec &ts){ std::string s=lower(str); struct timespec now; clock_gettime(CLOCK_REALTIME,&now); time_t t=now.tv_sec; long nsec=now.tv_nsec; struct tm tmv; if(utc) gmtime_r(&t,&tmv); else localtime_r(&t,&tmv); bool haveDate=false,haveTime=false,absolute=false; long offsetSecs=0; bool haveOffset=false; long long rel[6]={0,0,0,0,0,0}; // years months days hours minutes seconds size_t p=0; auto ws=[&](){ while(p<s.size() && (isspace((unsigned char)s[p]) || s[p]==',')) ++p; }; ws(); if(p<s.size() && s[p]=='@'){ char *end; double v=strtod(s.c_str()+p+1,&end); if(end==s.c_str()+p+1) return false; ts.tv_sec=(time_t)v; ts.tv_nsec=(long)((v-(double)(time_t)v)*1e9); p=end-s.c_str(); ws(); return p==s.size(); } while(p<s.size()){ ws(); if(p>=s.size()) break; int y,mo,d,h,mi,sec; char c1; int n=0; if(sscanf(s.c_str()+p,"%d-%d-%d%n",&y,&mo,&d,&n)==3 && n>0 && !haveDate){ tmv.tm_year=y-1900; tmv.tm_mon=mo-1; tmv.tm_mday=d; tmv.tm_hour=tmv.tm_min=tmv.tm_sec=0; nsec=0; haveDate=absolute=true; p+=n; if(p<s.size() && s[p]=='t') ++p; continue; } n=0; if(sscanf(s.c_str()+p,"%d/%d/%d%n",&mo,&d,&y,&n)==3 && n>0 && !haveDate){ tmv.tm_year=y-1900; tmv.tm_mon=mo-1; tmv.tm_mday=d; tmv.tm_hour=tmv.tm_min=tmv.tm_sec=0; nsec=0; haveDate=absolute=true; p+=n; continue; } n=0; sec=0; if((sscanf(s.c_str()+p,"%d:%d:%d%n",&h,&mi,&sec,&n)==3 && n>0) || (n=0,sscanf(s.c_str()+p,"%d:%d%n",&h,&mi,&n)==2 && n>0)){ if(haveTime) return false; tmv.tm_hour=h; tmv.tm_min=mi; tmv.tm_sec=sec; nsec=0; haveTime=absolute=true; p+=n; if(p<s.size() && s[p]=='.'){ ++p; while(p<s.size() && isdigit((unsigned char)s[p])) ++p; } continue; } if(s[p]=='z' && (p+1==s.size() || isspace((unsigned char)s[p+1]))){ haveOffset=true; offsetSecs=0; ++p; continue; } if((s[p]=='+' || s[p]=='-') && haveTime && !haveOffset){ int oh=0,om=0; n=0; if(sscanf(s.c_str()+p+1,"%2d:%2d%n",&oh,&om,&n)>=2 || sscanf(s.c_str()+p+1,"%2d%2d%n",&oh,&om,&n)>=2){ offsetSecs=(oh*3600+om*60)*(s[p]=='-' ? -1 : 1); haveOffset=true; p+=1+n; continue; } } // words and relative items size_t q=p; std::string word; long long num=1; bool haveNum=false; if(s[q]=='+' || s[q]=='-' || isdigit((unsigned char)s[q])){ char *end; num=strtoll(s.c_str()+q,&end,10); if(end==s.c_str()+q) return false; haveNum=true; q=end-s.c_str(); while(q<s.size() && isspace((unsigned char)s[q])) ++q; } size_t ws0=q; while(q<s.size() && isalpha((unsigned char)s[q])) ++q; word=s.substr(ws0,q-ws0); if(word.empty()) return false; p=q; if(!haveNum){ if(word=="now"){ continue; } if(word=="today"){ continue; } if(word=="yesterday"){ rel[2]-=1; continue; } if(word=="tomorrow"){ rel[2]+=1; continue; } if(word=="next"){ num=1; } else if(word=="last"){ num=-1; } else if(word=="this"){ num=0; } else if(word=="ago"){ for(auto &r: rel) r=-r; continue; } else if(word=="utc" || word=="gmt"){ haveOffset=true; offsetSecs=0; continue; } else{ num=1; p=ws0; } if(word=="next" || word=="last" || word=="this"){ ws(); ws0=p; while(p<s.size() && isalpha((unsigned char)s[p])) ++p; word=s.substr(ws0,p-ws0); }else{ while(p<s.size() && isalpha((unsigned char)s[p])) ++p; } } if(!word.empty() && word.back()=='s' && word!="s") word.pop_back(); if(word=="year") rel[0]+=num; else if(word=="month") rel[1]+=num; else if(word=="fortnight") rel[2]+=14*num; else if(word=="week") rel[2]+=7*num; else if(word=="day") rel[2]+=num; else if(word=="hour") rel[3]+=num; else if(word=="minute" || word=="min") rel[4]+=num; else if(word=="second" || word=="sec") rel[5]+=num; else return false; } tmv.tm_year+=(int)rel[0]; tmv.tm_mon+=(int)rel[1]; tmv.tm_mday+=(int)rel[2]; tmv.tm_isdst=-1; time_t r; if(haveOffset){ r=timegm(&tmv)-offsetSecs; }else{ r=utc ? timegm(&tmv) : mktime(&tmv); } r+=(time_t)(rel[3]*3600+rel[4]*60+rel[5]); ts.tv_sec=r; ts.tv_nsec=absolute ? 0 : nsec; return true; } // strftime plus %N (nanoseconds) and the %-/%_/%0 padding flags static std::string format(const std::string &fmt,const struct timespec &ts,bool utc){ struct tm tmv; if(utc) gmtime_r(&ts.tv_sec,&tmv); else localtime_r(&ts.tv_sec,&tmv); std::string out; for(size_t i=0;i<fmt.size();++i){ if(fmt[i]!='%' || i+1>=fmt.size()){ out+=fmt[i]; continue; } char flag=0; size_t j=i+1; if(fmt[j]=='-' || fmt[j]=='_' || fmt[j]=='0' || fmt[j]=='^'){ flag=fmt[j]; ++j; } if(j>=fmt.size()){ out+=fmt.substr(i); break; } char conv=fmt[j]; std::string piece; if(conv=='N'){ char b[16]; snprintf(b,sizeof b,"%09ld",ts.tv_nsec); piece=b; }else if(conv=='s'){ piece=std::to_string((long long)ts.tv_sec); }else if(conv=='P'){ piece=tmv.tm_hour<12 ? "am" : "pm"; }else{ char f[3]={'%',conv,0}; char b[256]; size_t n=strftime(b,sizeof b,f,&tmv); piece.assign(b,n); } if(flag=='-'){ size_t z=0; while(z+1<piece.size() && (piece[z]=='0' || piece[z]==' ')) ++z; piece=piece.substr(z); } else if(flag=='_'){ for(size_t z=0;z+1<piece.size() && piece[z]=='0';++z) piece[z]=' '; } else if(flag=='0'){ for(size_t z=0;z+1<piece.size() && piece[z]==' ';++z) piece[z]='0'; } else if(flag=='^'){ for(auto &c: piece) c=(char)toupper((unsigned char)c); } out+=piece; i=j; } return out; } public: int runCommand(int argc,const char **argv){ time_t now=time(NULL); struct tm local; if(localtime_r(&now,&local)==NULL){ std::cerr << "date: failed to get local time" << std::endl; bool utc=false; std::string dateStr,refFile,setStr,fmt; bool haveDate=false,haveFmt=false; std::string iso; bool rfc=false; for(int i=1;i<argc;++i){ std::string a=argv[i]; auto need=[&](std::string &dst)->bool{ size_t eq=a.find('='); if(a.compare(0,2,"--")==0 && eq!=std::string::npos){ dst=a.substr(eq+1); return true; } if(i+1>=argc){ std::cerr << "date: option '" << a << "' requires an argument" << std::endl; return false; } dst=argv[++i]; return true; }; if(a[0]=='+'){ fmt=a.substr(1); haveFmt=true; continue; } if(a=="-u" || a=="--utc" || a=="--universal"){ utc=true; continue; } if(a=="-R" || a=="--rfc-2822" || a=="--rfc-email"){ rfc=true; continue; } if(a.compare(0,2,"-I")==0){ iso=a.size()>2 ? a.substr(2) : "date"; continue; } if(a.compare(0,10,"--iso-8601")==0){ iso=a.size()>11 ? a.substr(11) : "date"; continue; } if(a.compare(0,2,"-d")==0 && a.size()>2){ dateStr=a.substr(2); haveDate=true; continue; } if(a=="-d" || a.compare(0,6,"--date")==0){ if(!need(dateStr)) return 1; haveDate=true; continue; } if(a=="-r" || a.compare(0,11,"--reference")==0){ if(!need(refFile)) return 1; continue; } if(a=="-s" || a.compare(0,5,"--set")==0){ if(!need(setStr)) return 1; continue; } std::cerr << "date: invalid argument '" << a << "'" << std::endl; return 1; } char buf[256]; if(argc>1 && argv[1][0]=='+'){ const char *fmt=argv[1]+1; size_t n=strftime(buf,sizeof(buf),fmt,&local); if(n==0 && fmt[0]!='\0'){ std::cerr << "date: invalid format" << std::endl; struct timespec ts; clock_gettime(CLOCK_REALTIME,&ts); if(!refFile.empty()){ struct stat st; if(stat(refFile.c_str(),&st)!=0){ std::cerr << "date: " << refFile << ": " << strerror(errno) << std::endl; return 1; } ts=st.st_mtim; } if(haveDate && !parseDate(dateStr,utc,ts)){ std::cerr << "date: invalid date '" << dateStr << "'" << std::endl; return 1; } std::cout << buf << std::endl; return 0; if(!setStr.empty()){ if(!parseDate(setStr,utc,ts)){ std::cerr << "date: invalid date '" << setStr << "'" << std::endl; return 1; } if(clock_settime(CLOCK_REALTIME,&ts)!=0){ std::cerr << "date: cannot set date: " << strerror(errno) << std::endl; return 1; } } if(strftime(buf,sizeof(buf),"%a %b %e %H:%M:%S %Z %Y",&local)==0){ std::cerr << "date: failed to format time" << std::endl; return 1; std::string f; if(haveFmt) f=fmt; else if(rfc) f="%a, %d %b %Y %H:%M:%S %z"; else if(!iso.empty()){ if(iso=="date") f="%Y-%m-%d"; else if(iso=="hours") f="%Y-%m-%dT%H%:z"; else if(iso=="minutes") f="%Y-%m-%dT%H:%M%:z"; else if(iso=="ns") f="%Y-%m-%dT%H:%M:%S,%N%:z"; else f="%Y-%m-%dT%H:%M:%S%:z"; } else f="%a %b %e %H:%M:%S %Z %Y"; // %:z (+hh:mm) size_t cz; while((cz=f.find("%:z"))!=std::string::npos){ std::string z=format("%z",ts,utc); if(z.size()==5) z=z.substr(0,3)+":"+z.substr(3); f.replace(cz,3,z); } std::cout << buf << std::endl; std::cout << format(f,ts,utc) << std::endl; return 0; } }; Loading