D中日历写法与组件式编程

/*D日历用带区间的组件式编程,来简化复杂任务为可管理片段示例.任务是,给定`年`,生成代表该年日历的行.展示如何清晰易读读写易重用复杂组件.*/

导入.算法;
导入.转换;
导入.日期时间;
导入.函数;
导入.区间;
导入.标io:写行,写格式行,标错误;
导入.;


/*返回:N个空格串.*/
 空格()( n)
{
    导入.数组:重复;
     重复(" ",n);
}


/*返回:给定年中的日期区间.*/
 年中日期()()
{
     日期(,1,1)
        .再现!((a,n)=>a[n-1]+1.)
        .直到!(a=>a.>);
}

单元测试
{
     日期=年中日期(2013);
    断定(!日期.空的);
    断定(日期.==日期(2013,1,1));

    //检查增量
    日期.弹前();
    断定(日期.==日期(2013,1,2));

    //检查按月
    每一(i;2..31){
        断定(!日期.空的);
        日期.弹前();
    }
    断定(!日期.空的);
    断定(日期.==日期(2013,1,31));
    日期.弹前();

    断定(!日期.空的);
    断定(日期.==日期(2013,2,1));
}

单元测试
{
    //检查年长
     日期=年中日期(2013);
    每一(i;0..365){
        断定(!日期.空的);
        日期.弹前();
    }
    断定(日期.空的);
}


/*验证给定区间日期输入区间的便捷模板.*/
 是日期区间(R)
{
     是日期区间=是输入区间!R&&(元素类型!R:日期);
}

 断定(是日期区间!(的型(年中日期(1))));


/*按等价元素分块输入区间.函数以输入区间为参,拆分为(`函数属性`值一样的)等价相邻元素子区间.
请注意,中间不等价元素分隔的等价元素在单独子区间中;本函数只考虑相邻等价.类似`std.algorithm.group`,但当每个组中元素在结果中必须`可迭代`时,不能用后者.
参数:G=F.
返回值,必须能用==进行比较.
r=待分块区间.返回:区间的相同属性值区间*/
 块按(别名 F,区间)(区间 r)
    (是输入区间!区间&&
        (的型(
            F(元素类型!区间.初化)==F(元素类型!区间.初化)
        ))//有==,F为属性函数
    )
{
    别名 G=一元函数!F;
    别名 属性类型=的型(属性(r.));//类型

     {//C为当前属性,P为上个属性
         区间 r; 属性类型 C;
        @属性  空的()
        {
             r.空的||!(C==G(r.));
        }
        @属性 元素类型!区间 前(){ r.;}
         弹前()
        {
            断定(!r.空的);
            r.弹前();
        }
    }

      块按
    {
         区间 r; 属性类型 P;
        (区间 _r)
        {
            r=_r;
            (!空的)P=G(r.);
        }
        @属性  空的(){ r.空的;}
        @属性 ()
        {
            断定(!r.空的);(r,P);
        }
         弹前()
        {
            断定(!r.空的);
            (!r.空的&&G(r.)==P)
            {
                r.弹前();
            }
            (!r.空的)P=G(r.);
        }
         (是前向区间!区间)
        {
            @属性 块按 保存()
            {//可保存
                块按 复制=;
                复制.r=r.保存; 复制;
            }
        }
    }
     块按(r);
}//满足某个属性的分组区间.

///
单元测试
{
     区间=[ [1,1], [1,1], [1,2], [2,2], [2,3], [2,3], [3,3] ];

     按X=块按!(a=>a[0])(区间);
     期望1=[
        [[1,1],[1,1],[1,2]],
        [[2,2],[2,3],[2,3]],[[3,3]]
    ];//3,3,1
    每一(e;按X)
    {
        断定(!期望1.空的);
        断定(e.相等(期望1.));
        期望1.弹前();
    }

     按Y=块按!(a=>a[1])(区间);
     期望2=[
        [[1,1],[1,1]],
        [[1,2],[2,2]],
        [[2,3],[2,3],[3,3]]
    ];//两个期望不一样.//2,2,3
    每一(e;按Y)
    {
        断定(!期望2.空的);
        断定(e.相等(期望2.));
        期望2.弹前();
    }
}


/*按月分块日期的给定输入区间.
返回:其中每个子区间为同月日期的`系列`区间*/
 按月(I)(I 日期)
    (是日期区间!I)
{
     日期.块按!(a=>a.());
}

单元测试
{
    =年中日期(2013).按月();
    =1;
    {
        断定(!.空的);
        断定(..==日期(2013,,1));.弹前();//每月的前为月的1号.
    }(++<=12);

    断定(.空的);
}


/*按周分块日期的给定输入区间.
返回:其中每个子区间为同星期日期的`系列`区间,从星期天开始.*/
 按星期(I)(I 日期)(是日期区间!I)
{//是可复用的.
      按星期
    {
        I r;
        @属性  空的(){ r.空的;}
        @属性 ()
        {
             直到!((日期 a)=>a.星期中天==星期中天.星期六)(r,打开右.no);//不要右边.
        }//非常强大.处理了部分周的问题
         弹前()
        {
            断定(!r.空的);r.弹前();
            (!r.空的&&r..星期中天!=星期中天.太阳)r.弹前();
        }
    }
     按星期(日期);
}

单元测试
{
    =年中日期(2013).按星期();
    断定(!.空的);
    断定(相等(.,[
        日期(2013,1,1), //星期二
        日期(2013,1,2), //星期三
        日期(2013,1,3), //星期四
        日期(2013,1,4), //星期五
        日期(2013,1,5), //星期六
    ]));.弹前();

    断定(!.空的);
    断定(相等(.,[
        日期(2013,1,6), //星期日
        日期(2013,1,7), //星期一
        日期(2013,1,8), //星期二
        日期(2013,1,9), //星期三
        日期(2013,1,10),//星期四
        日期(2013,1,11),//星期五
        日期(2013,1,12),//星期六
    ]));.弹前();

    断定(!.空的);
    断定(..==日期(2013,1,13));
}


///格式化输出中每天列数.
 每天列=3;
///格式化输出中每周列数.
 每星期列=7*每天列;

/*格式化`周区间`为`串区间`.
格式化每一天为月中数字表示,用3空格填充.
参数:周=(周中连续日期的)日期区间的区间*/
 格式星期(区间)(区间 周)纯 不抛
    (是输入区间!区间&&是输入区间!(元素类型!区间)&&(元素类型!(元素类型!区间)==日期))
{
     星期串
    {
        区间 r;
        @属性  空的(){ r.空的;}

        ()
        (s){断定(s.长度==每星期列);}{
             缓冲=附加器!();

            //插入足够空隙将第一天与其各自的
            //星期几对齐.
            断定(!r..空的);
             开始天=r...星期中天;
            缓冲.(空格(每天列*开始天));

            //格式化每天为自身单元并附加到目标串.
            []=映射!((日期 d)=>" %2d".格式(d.))(r.).数组;
            断定(.长度<=7-开始天);.复制(缓冲);

            //如是短星期,在尾插入更多空格来填充
            //(如在月底).
            (.长度<7-开始天)
                缓冲.(空格(每天列*(7-开始天-.长度)));

             缓冲.数据;
        }//格式星期

         弹前(){r.弹前();}
    }
     星期串();
}

单元测试
{
     一月2013=年中日期(2013)
        .按月().//选择2013年1月来测试
        .按星期()
        .格式星期()
        .合并("\n");

    断定(一月2013==
        "        1  2  3  4  5\n"~
        "  6  7  8  9 10 11 12\n"~
        " 13 14 15 16 17 18 19\n"~
        " 20 21 22 23 24 25 26\n"~
        " 27 28 29 30 31"
    );
}


/*按每周列居中格式化月份名称.*/
 月标题()(月 月)
{
     不变 []月名=[
        "一月","二月","三月","四月","五月","六月",
        "七月","八月","九月","十月","11月","12月"
    ];
     断定(月名.长度==12);

    //确定需要月份名前后空格数
    //在格式周上按月居中
    =月名[-1];
    断定(.长度<每星期列);
     在前=(每星期列-.长度)/2;
     在后=每星期列-.长度-在前;

     空格(在前)~~空格(在后);
}

单元测试
{
    断定(月标题(.一月).长度==每星期列);
}


/*编译时辅助函数,创建包含星期几缩写名称的串,可用作有中星期标头.与每日列对齐.*/
 造工作日头()()
{
     工作日名=["Su","Mo","Tu","We","Th","Fr","Sa"];
     工作日名
        .映射!(s=>格式(" %.*s",每天列,s))
        .合并();
}

/*包含周缩写名标题,用作每月头.用D的`CTFE`功能编译时计算的串.*/
 工作日头=造工作日头();

单元测试
{
    断定(工作日头.长度==每星期列);
    断定(工作日头==" Su Mo Tu We Th Fr Sa");
}


/*格式化月.
参数:月天=月中连续日期区间.
返回:格式化后月份每行串区间.*/
 格式月(区间)(区间 月天)
    (是输入区间!区间&&(元素类型!区间==日期)){
    断定(!月天.空的);
    断定(月天..==1);
}{
    (
        [月标题(月天..)],
        [工作日头],
        月天.按星期().格式星期());
}

单元测试
{
     月格式=年中日期(2013)
        .按月().//选择`一月`作为测试用例
        .格式月()
        .合并("\n");

    断定(月格式==
        "         一月        \n"~
        " Su Mo Tu We Th Fr Sa\n"~
        "        1  2  3  4  5\n"~
        "  6  7  8  9 10 11 12\n"~
        " 13 14 15 16 17 18 19\n"~
        " 20 21 22 23 24 25 26\n"~
        " 27 28 29 30 31"
    );
}


/*格式化月区间.
参数:月=月中日期区间的区间.
返回:每月的格式化行区间的区间.*/
 格式月(区间)(区间 月)纯 不抛
    (是输入区间!区间&&(元素类型!(元素类型!区间)==日期))
{
    .映射!格式月;
}


/*水平粘贴矩形字符块的前向区间.每个矩形块按固定宽度区间串表示.如果块比其他块长,用空格填充.
参数:R=固定宽串区间区间.
分隔宽度=每月间插入的空格数.
返回:每月格式化区间区间.*/
 粘贴块(区间)(区间 R, 分隔宽度)
    (是前向区间!区间&&(元素类型!(元素类型!区间):))
{
    {
        区间 R; 分隔;[]列宽; _空的;

        (区间 _R, _分隔)
        {
            R=_R;分隔=_分隔;_空的=R.空的;

            //存储每列宽,以便子区间数据不够时填充空格
            每一(r;R.保存)
            {
                列宽~=r.空的?0:r..长度;
            }
        }

        @属性  空的(){ _空的;}
        @属性 ()
        {
            
                //同时迭代R和列宽
                压缩(R.保存,列宽)

                //映射每个子区间到它的`前`元素,或如已空了,则用空格填充
                .映射!(a=>a[0].空的?空格(a[1]):a[0].)
                //合并成一条线
                .合并(分隔);
        }

        ///从每个子区间弹出一个元素.
         弹前()
        {
            断定(!空的);
            _空的=;//假设弹后无更多数据(我们很懒)
            每一(引用 r;R)
            {
                (!r.空的)
                {
                    r.弹前();
                    (!r.空的)_空的=;
                    //好吧,毕竟还有数据
                }
            }
        }
    }
     断定(是输入区间!);

     分割器=空格(分隔宽度);
    (R,分割器);
}

单元测试
{
    //做漂亮的月历.单元测试怎么样?:)
    =年中日期(2013).按月().(3)
              .格式月()
              .数组()
              .粘贴块(1)
              .合并("\n");
    断定(==
        "       January              February                March        \n"~
        " Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa\n"~
        "        1  2  3  4  5                  1  2                  1  2\n"~
        "  6  7  8  9 10 11 12   3  4  5  6  7  8  9   3  4  5  6  7  8  9\n"~
        " 13 14 15 16 17 18 19  10 11 12 13 14 15 16  10 11 12 13 14 15 16\n"~
        " 20 21 22 23 24 25 26  17 18 19 20 21 22 23  17 18 19 20 21 22 23\n"~
        " 27 28 29 30 31        24 25 26 27 28        24 25 26 27 28 29 30\n"~
        "                                             31"
    );
}


//下面块是简单替换std.range.chunks
//早先限制
//不允许其与不可切片区间一起用.
//在2.064中解除,
 (__版本__<2064L)
{
    (区间)(区间 r, n)
    {
        {
            区间 r;
             n;

            @属性  空的(){ r.空的;}
            @属性 (){ r.保存.(n);}
             弹前()
            {
                =n;
                (-->0&&!r.空的)r.弹前();
            }
        }
        (r,n);
    }

    单元测试
    {
         r=[1,2,3,4,5,6,7];
         c=r.(3);
        断定(c.相等([[1,2,3],[4,5,6],[7]]));
    }
}


/*格式化年.
参数:year=显示日历年.
monthsPerRow=每行多少月.
返回:表示格式化年的串区间.*/
 格式年()(, 每行月)
{
     空间列=1;

    
        //首先生成给定年所有日期
        年中日期()

        //按月分组
        .按月()

        //分组月为水平行
        .(每行月)

        //格式化每行
        .映射!(r=>
                //格式化每月
                r.格式月()
                 //在行缓冲区中存储每月格式
                 .数组()
                 //水平粘贴每月行在一起
                 .粘贴块(空间列)
                 .合并("\n"))
        //每行间插入空行
        .合并("\n\n");
}

 ([]实参)
{
    //默认年从当前系统时钟获取.
    =(转换(日期).当前时间).;
    //很简单:从命令行解析年:
    (实参.长度==2)
    {=!(实参[1]);
    }

    //打印日历
     每行月=3;写行(格式年(,每行月));

     0;
}
posted @   zjh6  阅读(12)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示