关于日期时间:SQL-将24小时(“军用”)时间(2145)转换为”AM/PM时间”(9:45 PM)

SQL - Converting 24-hour (“military”) time (2145) to “AM/PM time” (9:45 pm)

我使用的两个字段存储为小型军事结构时间。编辑我正在运行IBM Informix动态服务器版本10.00.fc9

求和结束

样本值

1
2
3
4
5
beg_tm   545
end_tm   815

beg_tm   1245
end_tm   1330

样本输出

1
2
3
4
5
beg_tm   5:45 am
end_tm   8:15 am

beg_tm   12:45 pm
end_tm   1:30 pm

我在Perl中使用过这种方法,但我在寻找一种使用SQL和case语句的方法。

这是可能的吗?编辑

本质上,这种格式必须在ACE报告中使用。我找不到一种方法在输出部分使用

1
2
IF(beg_tm>=1300) THEN
beg_tm = vbeg_tm - 1200

其中vbeg_tm是声明的char(4)变量编辑工作小时数大于1300(2230除外!!)

1
SELECT substr((beg_tm-1200),0,1)||":"||substr((beg_tm-1200),2,2) FROM mtg_rec WHERE beg_tm>=1300;

这项工作小时数<1200(有时….10:40不及格)

1
SELECT substr((mtg_rec.beg_tm),0,(LENGTH(CAST(beg_tm AS VARCHAR(4)))-2))||":"||(substr((mtg_rec.beg_tm),2,2))||" am" beg_tm FROM mtg_rec WHERE mtg_no = 1;

编辑乔纳森·莱弗勒表达方法中的转换语法变化

1
2
3
4
5
6
7
8
9
10
SELECT  beg_tm,
        CAST((MOD(beg_tm/100 + 11, 12) + 1) AS VARCHAR(2)) || ':' ||
        SUBSTRING(CAST((MOD(beg_tm, 100) + 100) AS CHAR(3)) FROM 2) ||
        SUBSTRING(' am pm' FROM (MOD(CAST((beg_tm/1200) AS INT), 2) * 3) + 1 FOR 3),
        end_tm,
        CAST((MOD(end_tm/100 + 11, 12) + 1) AS VARCHAR(2)) || ':' ||
        SUBSTRING(CAST((MOD(end_tm, 100) + 100) AS CHAR(3)) FROM 2) ||
        SUBSTRING(' am pm' FROM (MOD(CAST((end_tm/1200) AS INT), 2) * 3) + 1 FOR 3)
      FROM mtg_rec
      WHERE mtg_no = 39;


请注意,在SO 440061中有一些有用的信息,关于在12小时到24小时之间转换时间符号(与此转换相反);这并不简单,因为12:45 AM比1:15早半小时。

接下来,请注意,Informix(IDS-Informix动态服务器)7.31版最终在2009-09-30结束服务;它不再是受支持的产品。

您的版本号应该更精确;例如,7.30.uc1和7.31.ud8之间存在相当大的差异。

但是,您应该能够根据需要使用to_char()函数来格式化时间。虽然这个参考是关于IDS12.10信息中心的,但我相信你可以在7.31中使用它(不一定在7.30中使用,但在过去的十年中大部分时间里你不应该使用它)。

有一个用于24小时的格式说明符"%r",它说。它还引用了"gl_datetime",其中它说:"%i"给您12小时的时间,"%p"给您AM/PM指示器。我还找到了一个7.31.ud8的ID实例来验证这一点:

1
2
3
4
5
6
7
8
9
SELECT to_char(datetime(2009-01-01 16:15:14) YEAR TO SECOND, '%I:%M %p')
    FROM dual;

04:15 PM

SELECT to_char(datetime(2009-01-01 16:15:14) YEAR TO SECOND, '%1.1I:%M %p')
    FROM dual;

4:15 PM

我从重新阅读这个问题中看到,实际上您有0000到2359范围内的smallint值,需要对这些值进行转换。通常,我会指出Informix有一种类型来存储这些值——从datetime hour到minute——但是我承认它在磁盘上占用了3个字节,而不仅仅是2个字节,所以它没有一个小符号那么紧凑。

Steve Kass展示了SQL Server符号:

1
2
3
4
5
6
7
SELECT
  CAST((@milTime/100+11)%12+1 AS VARCHAR(2))
 +':'
 +SUBSTRING(CAST((@milTime%100+100) AS CHAR(3)),2,2)
 +' '
 +SUBSTRING('ap',@milTime/1200%2+1,1)
 +'m';

让时间正确的诀窍是整洁的-谢谢史蒂夫!

转换为IDS11.50的Informix,假设表是:

1
2
3
4
5
6
7
8
CREATE TEMP TABLE times(begin_tm SMALLINT NOT NULL);

SELECT  begin_tm,
        (MOD(begin_tm/100 + 11, 12) + 1)::VARCHAR(2) || ':' ||
        SUBSTRING((MOD(begin_tm, 100) + 100)::CHAR(3) FROM 2) || ' ' ||
        SUBSTRING("ampm" FROM (MOD((begin_tm/1200)::INT, 2) * 2) + 1 FOR 2)
      FROM times
      ORDER BY begin_tm;

使用FROM和FOR的子字符串表示法是标准的SQL表示法-很奇怪,但确实如此。

示例结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
     0    12:00 am
     1    12:01 am
    59    12:59 am
   100    1:00 am  
   559    5:59 am  
   600    6:00 am  
   601    6:01 am  
   959    9:59 am  
  1000    10:00 am
  1159    11:59 am
  1200    12:00 pm
  1201    12:01 pm
  1259    12:59 pm
  1300    1:00 pm  
  2159    9:59 pm  
  2200    10:00 pm
  2359    11:59 pm
  2400    12:00 am

注意:值559-601在列表中,因为在缺少"转换为整数"的情况下,我遇到了舍入而不是截断的问题。

现在,这已经在IDS11.50上进行了测试;IDS7.3x将没有强制转换符号。不过,这不是问题,下一条评论是要处理的……

作为一个如何在没有条件等的情况下用SQL编写表达式的练习,这很有趣,但是如果有人在整个套件中不止一次地编写了这个表达式,我会因为缺乏模块化而将它们射杀。显然,这需要一个存储过程-存储过程不需要(显式)强制转换或其他一些技巧,尽管分配强制执行隐式强制转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE PROCEDURE ampm_time(tm SMALLINT) RETURNING CHAR(8);
    DEFINE hh SMALLINT;
    DEFINE mm SMALLINT;
    DEFINE am SMALLINT;
    DEFINE m3 CHAR(3);
    DEFINE a3 CHAR(3);
    LET hh = MOD(tm / 100 + 11, 12) + 1;
    LET mm = MOD(tm, 100) + 100;
    LET am = MOD(tm / 1200, 2);
    LET m3 = mm;
    IF am = 0
    THEN LET a3 = ' am';
    ELSE LET a3 = ' pm';
    END IF;
    RETURN (hh || ':' || m3[2,3] || a3);
END PROCEDURE;

Informix'[2,3]'表示法是子字符串运算符的原始形式;原始表示法是因为(由于仍然无法实现的原因)下标必须是文本整数(不是变量,不是表达式)。它恰好在这里有效地工作;一般来说,它是令人沮丧的。

这个存储过程应该可以在任何版本的Informix上工作(联机5.x、SE 7.x、ID 7.x或9.x、10.00、11.x、12.x),您可以亲自动手。

要说明表达式和存储过程的等效性(上的次要变量):

1
2
3
4
5
6
7
SELECT  begin_tm,
        (MOD(begin_tm/100 + 11, 12) + 1)::VARCHAR(2) || ':' ||
        SUBSTRING((MOD(begin_tm, 100) + 100)::CHAR(3) FROM 2) ||
        SUBSTRING(' am pm' FROM (MOD((begin_tm/1200)::INT, 2) * 3) + 1 FOR 3),
        ampm_time(begin_tm)
      FROM times
      ORDER BY begin_tm;

结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
     0  12:00 am        12:00 am
     1  12:01 am        12:01 am
    59  12:59 am        12:59 am
   100  1:00 am         1:00 am
   559  5:59 am         5:59 am
   600  6:00 am         6:00 pm
   601  6:01 am         6:01 pm
   959  9:59 am         9:59 pm
  1000  10:00 am        10:00 pm
  1159  11:59 am        11:59 pm
  1200  12:00 pm        12:00 pm
  1201  12:01 pm        12:01 pm
  1259  12:59 pm        12:59 pm
  1300  1:00 pm         1:00 pm
  2159  9:59 pm         9:59 pm
  2200  10:00 pm        10:00 pm
  2359  11:59 pm        11:59 pm
  2400  12:00 am        12:00 am

此存储过程现在可以在ACE报表内的单个select语句中多次使用,而无需进一步的ADO。

[在收到原始海报关于不工作的评论后…]

IDS 7.31不处理传递给mod()函数的非整数值。因此,这些划分必须存储在一个显式的整数变量中-因此:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CREATE PROCEDURE ampm_time(tm SMALLINT) RETURNING CHAR(8);
    DEFINE i2 SMALLINT;
    DEFINE hh SMALLINT;
    DEFINE mm SMALLINT;
    DEFINE am SMALLINT;
    DEFINE m3 CHAR(3);
    DEFINE a3 CHAR(3);
    LET i2 = tm / 100;
    LET hh = MOD(i2 + 11, 12) + 1;
    LET mm = MOD(tm, 100) + 100;
    LET i2 = tm / 1200;
    LET am = MOD(i2, 2);
    LET m3 = mm;
    IF am = 0
    THEN LET a3 = ' am';
    ELSE LET a3 = ' pm';
    END IF;
    RETURN (hh || ':' || m3[2,3] || a3);
END PROCEDURE;

这是在Solaris 10上的IDS7.31.UD8上测试的,并且工作正常。我不理解报告的语法错误;但是有一个外部的机会存在版本依赖性——为了以防万一,报告版本号和平台总是至关重要的。注意,我很小心地记录下各种各样的事情在哪里起作用;这不是意外,也不只是小题大做——这是基于多年的经验。


哦,jenzabar用户研究员乔纳森(Don’t Be太残酷,我们的模式。他们都是经过几十年的老literally)。你有没有问surprised CX -这在技术列表。我已经给你准备一个存储过程的雷达散射截面(RCS)Cx的。

SW

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
 Revision Information (Automatically maintained BY 'make' - DON'T CHANGE)
 -------------------------------------------------------------------------
 $Header$
 -------------------------------------------------------------------------
}
procedure       se_get_inttime
privilege       owner
description    "Get time from an integer field and return as datetime"
inputs          param_time integer     "Integer formatted time"
returns         datetime hour to minute"Time in datetime format"
notes          "Get time from an integer field and return as datetime"

begin procedure

DEFINE tm_str VARCHAR(255);
DEFINE h INTEGER;
DEFINE m INTEGER;

IF (param_time < 0 OR param_time > 2359) THEN
RAISE EXCEPTION -746, 0,"Invalid time format. Should be: 0 - 2359";
END IF

LET tm_str = LPAD(param_time, 4, 0);

LET h = SUBSTR(tm_str, 1, 2);

IF (h < 0 OR h > 23) THEN
RAISE EXCEPTION -746, 0,"Invalid time format. Should be: 0 - 2359";
END IF

LET m = SUBSTR(tm_str, 3, 4);

IF (m < 0 OR m > 59) THEN
RAISE EXCEPTION -746, 0,"Invalid time format. Should be: 0 - 2359";
END IF

RETURN TO_DATE(h || '
:' || m , '%R');

end procedure

grant
    execute to (group public)


MJV的第二次尝试仍然不起作用。(例如,对于0001,它给出0:1 AM。)

这里有一个T-SQL解决方案,应该能更好地工作。它可以通过使用连接和子字符串的适当语法来适应其他方言。

它也适用于军事时间2400(上午12:00),这可能是有用的。

1
2
3
4
5
6
7
SELECT
  CAST((@milTime/100+11)%12+1 AS VARCHAR(2))
 +':'
 +SUBSTRING(CAST((@milTime%100+100) AS CHAR(3)),2,2)
 +' '
 +SUBSTRING('ap',@milTime/1200%2+1,1)
 +'m';


这里是SteveKass的Informix解决方案的一个未经测试的端口。

Steve的解决方案本身经过了MS SQL Server的良好测试。我比以前的解决方案更喜欢它,因为到AM/PM时间的转换完全是代数完成的,不需要任何分支(使用case语句等)的帮助。

如果数字"军用时间"来自数据库,则用列名替换@miltime。@变量仅用于测试。

1
2
3
4
5
6
7
8
9
--declare @milTime int
--set @milTime = 1359
SELECT
  CAST(MOD((@milTime /100 + 11), 12) + 1 AS VARCHAR(2))
  ||':'
  ||SUBSTRING(CAST((@milTime%100 + 100) AS CHAR(3)) FROM 2 FOR 2)
  ||' '
  || SUBSTRING('ap' FROM (MOD(@milTime / 1200, 2) + 1) FOR 1)
  || 'm';

以下是我的[fixed]基于案例的SQL Server解决方案,供参考。

1
2
3
4
5
6
7
SELECT
  CASE ((@milTime / 100) % 12)
      WHEN 0 THEN '12'
      ELSE CAST((@milTime % 1200) / 100 AS VARCHAR(2))
  END
  + ':' + RIGHT('0' + CAST((@milTime % 100) AS VARCHAR(2)), 2)
  + CASE (@milTime / 1200) WHEN 0 THEN ' am' ELSE ' pm' END


Cheeshewithcheese说必须在ACE报告中完成,所以这是我的ACE报告…

在ace中将军事小时smallint转换为AM/PM格式的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
SELECT beg_tm, end_tm ...

define
variable utime CHAR(4)
variable ftime CHAR(7)
END

format

ON every ROW

let utime = beg_tm  {CAST beg_tm TO CHAR(4). do same FOR end_tm}

IF utime[1,2] ="00" THEN let ftime[1,3] ="12:"
IF utime[1,2] ="01" THEN let ftime[1,3] =" 1:"
IF utime[1,2] ="02" THEN let ftime[1,3] =" 2:"
IF utime[1,2] ="03" THEN let ftime[1,3] =" 3:"
IF utime[1,2] ="04" THEN let ftime[1,3] =" 4:"
IF utime[1,2] ="05" THEN let ftime[1,3] =" 5:"
IF utime[1,2] ="06" THEN let ftime[1,3] =" 6:"
IF utime[1,2] ="07" THEN let ftime[1,3] =" 7:"
IF utime[1,2] ="08" THEN let ftime[1,3] =" 8:"
IF utime[1,2] ="09" THEN let ftime[1,3] =" 9:"
IF utime[1,2] ="10" THEN let ftime[1,3] ="10:"
IF utime[1,2] ="11" THEN let ftime[1,3] ="11:"

IF utime[1,2] ="12" THEN let ftime[1,3] ="12:"
IF utime[1,2] ="13" THEN let ftime[1,3] =" 1:"
IF utime[1,2] ="14" THEN let ftime[1,3] =" 2:"
IF utime[1,2] ="15" THEN let ftime[1,3] =" 3:"
IF utime[1,2] ="16" THEN let ftime[1,3] =" 4:"
IF utime[1,2] ="17" THEN let ftime[1,3] =" 5:"
IF utime[1,2] ="18" THEN let ftime[1,3] =" 6:"
IF utime[1,2] ="19" THEN let ftime[1,3] =" 7:"
IF utime[1,2] ="20" THEN let ftime[1,3] =" 8:"
IF utime[1,2] ="21" THEN let ftime[1,3] =" 9:"
IF utime[1,2] ="22" THEN let ftime[1,3] ="10:"
IF utime[1,2] ="23" THEN let ftime[1,3] ="11:"

let ftime[4,5] = utime[3,4]  

IF utime[1,2] ="00"
OR utime[1,2] ="01"
OR utime[1,2] ="02"
OR utime[1,2] ="03"
OR utime[1,2] ="04"
OR utime[1,2] ="05"
OR utime[1,2] ="06"
OR utime[1,2] ="07"
OR utime[1,2] ="08"
OR utime[1,2] ="09"
OR utime[1,2] ="10"
OR utime[1,2] ="11" THEN let ftime[6,7] ="AM"

IF utime[1,2] ="12"
OR utime[1,2] ="13"
OR utime[1,2] ="14"
OR utime[1,2] ="15"
OR utime[1,2] ="16"
OR utime[1,2] ="17"
OR utime[1,2] ="18"
OR utime[1,2] ="19"
OR utime[1,2] ="20"
OR utime[1,2] ="21"
OR utime[1,2] ="22"
OR utime[1,2] ="23" THEN let ftime[6,7] ="PM"

print COLUMN 1,"UNFORMATTED TIME:", utime," = FORMATTED TIME:", ftime


不确定Informix,下面是我在Oracle中所做的(一些示例,但在家中没有测试):

  • 将整数转换成字符串:To_Char (milTime),例如1->'1',545->'545',1215->'1215'
  • 确保我们始终有一个四个字符的字符串:Right('0000'||To_Char(milTime), 4),例如1->'0001',545->'0545',1215->'1215'
  • 变成日期时间:To_Date (Right('0000'||To_Char(milTime), 4), 'HH24:MI')
  • 输出到所需格式:To_Char(To_Date(..),'HH:MI AM'),例如1->'00:01 am',545->'05:45 am',1215->'12:15 pm'
  • Oracle的too-date和to-char是专有的,但我确信有一些标准的SQL或Informix函数可以在不使用"计算"的情况下获得相同的结果。


    长手进近…但作品

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    SELECT  substr((mtg_rec.beg_tm-1200),0,1)||":"||substr((mtg_rec.beg_tm-1200),2,2)||" pm" beg_tm,
                substr((mtg_rec.end_tm-1200),0,1)||":"||substr((mtg_rec.end_tm-1200),2,2)||" pm" end_tm
        FROM    mtg_rec
        WHERE   mtg_rec.beg_tm BETWEEN 1300 AND 2159
                AND mtg_rec.end_tm BETWEEN 1300 AND 2159
        UNION
        SELECT  substr((mtg_rec.beg_tm-1200),0,1)||":"||substr((mtg_rec.beg_tm-1200),2,2)||" pm" beg_tm,
                substr((mtg_rec.end_tm-1200),0,2)||":"||substr((mtg_rec.end_tm-1200),3,2)||" pm" end_tm
        FROM    mtg_rec
        WHERE   mtg_rec.beg_tm BETWEEN 1300 AND 2159
                AND mtg_rec.end_tm BETWEEN 2159 AND 2400
        UNION
        SELECT  substr((mtg_rec.beg_tm-1200),0,2)||":"||substr((mtg_rec.beg_tm-1200),3,2)||" pm" beg_tm,
                substr((mtg_rec.end_tm-1200),0,2)||":"||substr((mtg_rec.end_tm-1200),3,2)||" pm" end_tm
                mtg_rec.days
        FROM    mtg_rec
        WHERE   mtg_rec.beg_tm BETWEEN 2159 AND 2400
                AND mtg_rec.end_tm BETWEEN 2159 AND 2400
        UNION
         SELECT substr((mtg_rec.beg_tm),0,1)||":"||(substr((mtg_rec.beg_tm),2,2))||" am" beg_tm,
                substr((mtg_rec.end_tm),0,1)||":"||(substr((mtg_rec.end_tm),2,2))||" am" end_tm
                mtg_rec.days
        FROM    mtg_rec
        WHERE   mtg_rec.beg_tm BETWEEN 0 AND 959
                AND mtg_rec.end_tm BETWEEN 0 AND 959
        UNION
         SELECT substr((mtg_rec.beg_tm),0,2)||":"||(substr((mtg_rec.beg_tm),3,2))||" am" beg_tm,
                substr((mtg_rec.end_tm),0,2)||":"||(substr((mtg_rec.end_tm),3,2))||" am" end_tm
                mtg_rec.days
        FROM    mtg_rec
        WHERE   mtg_rec.beg_tm BETWEEN 1000 AND 1259
                AND mtg_rec.end_tm BETWEEN 1000 AND 1259
        UNION
         SELECT CAST(beg_tm AS VARCHAR(4)),
                CAST(end_tm AS VARCHAR(4))
        FROM    mtg_rec
        WHERE   mtg_rec.beg_tm = 0
                AND mtg_rec.end_tm = 0
        INTO temp time_machine WITH no log;