diff --git a/src/utilities/convert/dseries2M.m b/src/utilities/convert/dseries2M.m
new file mode 100644
index 0000000000000000000000000000000000000000..9b3768f2b71a2baf38ea31ba992a03000045ecc1
--- /dev/null
+++ b/src/utilities/convert/dseries2M.m
@@ -0,0 +1,102 @@
+function ts = dseries2M(ds, method)
+
+% INPUTS
+% - ds       [dseries]    daily frequency object with n elements.
+% - method   [char]       1×m array, method used for the time aggregation, possible values are:
+%                            - 'arithmetic-average' (for rates),
+%                            - 'geometric-average' (for factors),
+%                            - 'sum' (for flow variables), and
+%                            - 'end-of-period' (for stock variables).
+%
+% OUTPUTS
+% - ts       [dseries]    monthly frequency object with m<n elements.
+
+% Copyright © 2020 Dynare Team
+%
+% This code is free software: you can redistribute it and/or modify
+% it under the terms of the GNU General Public License as published by
+% the Free Software Foundation, either version 3 of the License, or
+% (at your option) any later version.
+%
+% Dynare dates submodule is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+% GNU General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License
+% along with Dynare.  If not, see <http://www.gnu.org/licenses/>.
+
+    dm = dates2M(ds.dates);
+    dM = unique(dm); % dM.ndat will be the maximum number of periods in ts
+
+    tdata = NaN(dM.ndat, ds.vobs);
+
+    switch method
+      case 'arithmetic-average'
+        for i=1:dM.ndat
+            idm = find(dm==dM(i));
+            tdata(i,:) = mean(ds.data(idm,:));
+            if ~isalldays(dM(i), idm)
+                warning('Not all days are available in month %s.', date2string(dM(i)))
+            end
+        end
+      case 'geometric-average'
+        for i=1:dM.ndat
+            idm = find(dm==dM(i));
+            tdata(i,:) = prod(ds.data(idm,:), 1).^(1/length(idm));
+            if ~isalldays(dM(i), idm)
+                warning('Not all days are available in month %s.', date2string(dM(i)))
+            end
+        end
+      case 'sum'
+        for i=1:dM.ndat
+            idm = find(dm==dM(i));
+            tdata(i,:) = sum(ds.data(idm,:), 1);
+            if ~isalldays(dM(i), idm)
+                warning('Not all days are available in month %s.', date2string(dM(i)))
+            end
+        end
+      case 'end-of-period'
+        for i=1:dM.ndat
+            idm = find(dm==dM(i));
+            lastday = datevec(ds.dates.time(idm(end)));
+            tdata(i,:) = ds.data(idm(end),:);
+            if lastday(3)<expectedlastday(dM(i))
+                warning('The last available day is not the last observed day of %s.', date2string(dM(i)))
+            end
+        end
+      otherwise
+        error('Unknow method.')
+    end
+
+    ts = dseries(tdata, dM, ds.name, ds.tex);
+
+end
+
+function eld = expectedlastday(m)
+    if ismember(m.time(2), [4,6,9,11])
+        eld = 30;
+    elseif ismember(m.time(2), [1,3,5,7,8,10,12])
+        eld = 31;
+    else % February
+        if isleapyear(m.time(1))
+            eld = 29;
+        else
+            eld = 28;
+        end
+    end
+end
+
+function iad = isalldays(m, id)
+    if ismember(m.time(2), [4,6,9,11])
+        iad = length(id)==30;
+    elseif ismember(m.time(2), [1,3,5,7,8,10,12])
+        iad = length(id)==31;
+    else % February
+        if isleapyear(m.time(1))
+            iad = length(id)==29;
+        else
+            iad = length(id)==28;
+        end
+    end
+end
\ No newline at end of file
diff --git a/src/utilities/convert/dseries2Q.m b/src/utilities/convert/dseries2Q.m
new file mode 100644
index 0000000000000000000000000000000000000000..0e05bacae9a30efd8fba5b4e623ba8d05100d8c6
--- /dev/null
+++ b/src/utilities/convert/dseries2Q.m
@@ -0,0 +1,127 @@
+function ts = dseries2Q(ds, method)
+
+% INPUTS
+% - ds       [dseries]    daily or monthly frequency object with n elements.
+% - method   [char]       1×m array, method used for the time aggregation, possible values are:
+%                            - 'arithmetic-average' (for rates),
+%                            - 'geometric-average' (for factors),
+%                            - 'sum' (for flow variables), and
+%                            - 'end-of-period' (for stock variables).
+%
+% OUTPUTS
+% - ts       [dseries]    quaterly frequency object with m<n elements.
+
+% Copyright © 2020 Dynare Team
+%
+% This code is free software: you can redistribute it and/or modify
+% it under the terms of the GNU General Public License as published by
+% the Free Software Foundation, either version 3 of the License, or
+% (at your option) any later version.
+%
+% Dynare dates submodule is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+% GNU General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License
+% along with Dynare.  If not, see <http://www.gnu.org/licenses/>.
+
+    dq = dates2Q(ds.dates);
+    dQ = unique(dq); % dQ.ndat will be the maximum number of periods in ts
+
+    tdata = NaN(dQ.ndat, ds.vobs);
+
+    switch method
+      case 'arithmetic-average'
+        for i=1:dQ.ndat
+            idq = find(dq==dQ(i));
+            tdata(i,:) = mean(ds.data(idq,:));
+            if ds.dates.freq==365 && ~isalldays(dQ(i), idq)
+                warning('Not all days are available in %s.', date2string(dQ(i)))
+            end
+            if ds.dates.freq==12 && ~isequal(length(idq), 3)
+                warning('Not all months are available in %s.', date2string(dQ(i)))
+            end
+        end
+      case 'geometric-average'
+        for i=1:dQ.ndat
+            idq = find(dq==dQ(i));
+            tdata(i,:) = prod(ds.data(idq,:), 1).^(1/length(idq));
+            if ds.dates.freq==365 && ~isalldays(dQ(i), idq)
+                warning('Not all days are available in %s.', date2string(dQ(i)))
+            end
+            if ds.dates.freq==12 && ~isequal(length(idq), 3)
+                warning('Not all months are available in %s.', date2string(dQ(i)))
+            end
+        end
+      case 'sum'
+        for i=1:dQ.ndat
+            idq = find(dq==dQ(i));
+            tdata(i,:) = sum(ds.data(idq,:), 1);
+            if ds.dates.freq==365 && ~isalldays(dQ(i), idq)
+                warning('Not all days are available in %s.', date2string(dQ(i)))
+            end
+            if ds.dates.freq==12 && ~isequal(length(idq), 3)
+                warning('Not all months are available in %s.', date2string(dQ(i)))
+            end
+        end
+      case 'end-of-period'
+        for i=1:dQ.ndat
+            idq = find(dq==dQ(i));
+            tdata(i,:) = ds.data(idq(end),:);
+            if ds.dates.freq==365
+                lastday = datevec(ds.dates.time(idq(end)));
+                if lastday(3)<expectedlastday(dQ(i))
+                    warning('The last available day is not the last observed day of %s.', date2string(dQ(i)))
+                end
+            end
+            if ds.dates.freq==12
+                lastmonth = ds.dates.time(idq(end), 2);
+                if lastmonth<expectedlastmonth(dQ(i))
+                    warning('The last available day is not the last observed day of %s.', date2string(dQ(i)))
+                end
+            end
+        end
+      otherwise
+        error('Unknow method.')
+    end
+
+    ts = dseries(tdata, dQ, ds.name, ds.tex);
+
+end
+
+function eld = expectedlastday(q)
+    if ismember(q.time(2), [1,4])
+        eld = 31; % last day of March or December
+    else
+        eld = 30; % last day of June or September
+    end
+end
+
+function elm = expectedlastmonth(q)
+    switch q.time(2)
+      case 1
+        elm = 3;
+      case 2
+        elm = 6;
+      case 3
+        elm = 9;
+      case 4
+        elm = 12;
+      otherwise
+    end
+end
+
+function iad = isalldays(m, id)
+    if ismember(m.time(2), [3, 4])
+        iad = length(id)==92;
+    elseif m.time(2)==2
+        iad = length(id)==91;
+    else % contains February
+        if isleapyear(m.time(1))
+            iad = length(id)==91;
+        else
+            iad = length(id)==90;
+        end
+    end
+end
\ No newline at end of file