Commit 227f9ca6 authored by jan.koester's avatar jan.koester
Browse files

test

parent 42803035
Loading
Loading
Loading
Loading
+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
@@ -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;
        }
    };